summaryrefslogtreecommitdiff
path: root/lib/gem_extensions/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gem_extensions/active_record')
-rw-r--r--lib/gem_extensions/active_record/association.rb37
-rw-r--r--lib/gem_extensions/active_record/associations/builder/has_many.rb21
-rw-r--r--lib/gem_extensions/active_record/associations/builder/has_one.rb21
-rw-r--r--lib/gem_extensions/active_record/associations/has_many_through_association.rb18
-rw-r--r--lib/gem_extensions/active_record/associations/has_one_through_association.rb17
-rw-r--r--lib/gem_extensions/active_record/associations/preloader/through_association.rb22
-rw-r--r--lib/gem_extensions/active_record/configurable_disable_joins.rb17
-rw-r--r--lib/gem_extensions/active_record/delegate_cache.rb34
-rw-r--r--lib/gem_extensions/active_record/disable_joins/associations/association_scope.rb78
-rw-r--r--lib/gem_extensions/active_record/disable_joins/relation.rb43
10 files changed, 308 insertions, 0 deletions
diff --git a/lib/gem_extensions/active_record/association.rb b/lib/gem_extensions/active_record/association.rb
new file mode 100644
index 00000000000..91a9f45ce7e
--- /dev/null
+++ b/lib/gem_extensions/active_record/association.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module Association
+ extend ActiveSupport::Concern
+
+ attr_reader :disable_joins
+
+ def initialize(owner, reflection)
+ super
+
+ @disable_joins = @reflection.options[:disable_joins] || false
+ end
+
+ def scope
+ if disable_joins
+ DisableJoins::Associations::AssociationScope.create.scope(self)
+ else
+ super
+ end
+ end
+
+ def association_scope
+ if klass
+ @association_scope ||= begin # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ if disable_joins
+ DisableJoins::Associations::AssociationScope.scope(self)
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/associations/builder/has_many.rb b/lib/gem_extensions/active_record/associations/builder/has_many.rb
new file mode 100644
index 00000000000..7e51e632cc3
--- /dev/null
+++ b/lib/gem_extensions/active_record/associations/builder/has_many.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module Associations
+ module Builder
+ module HasMany
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def valid_options(options)
+ valid = super
+ valid += [:disable_joins] if options[:disable_joins] && options[:through]
+ valid
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/associations/builder/has_one.rb b/lib/gem_extensions/active_record/associations/builder/has_one.rb
new file mode 100644
index 00000000000..91765db8a5a
--- /dev/null
+++ b/lib/gem_extensions/active_record/associations/builder/has_one.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module Associations
+ module Builder
+ module HasOne
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def valid_options(options)
+ valid = super
+ valid += [:disable_joins] if options[:disable_joins] && options[:through]
+ valid
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/associations/has_many_through_association.rb b/lib/gem_extensions/active_record/associations/has_many_through_association.rb
new file mode 100644
index 00000000000..e7051e4d9cb
--- /dev/null
+++ b/lib/gem_extensions/active_record/associations/has_many_through_association.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module Associations
+ module HasManyThroughAssociation
+ extend ActiveSupport::Concern
+
+ def find_target
+ return [] unless target_reflection_has_associated_record?
+ return scope.to_a if disable_joins
+
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/associations/has_one_through_association.rb b/lib/gem_extensions/active_record/associations/has_one_through_association.rb
new file mode 100644
index 00000000000..1487392a4ea
--- /dev/null
+++ b/lib/gem_extensions/active_record/associations/has_one_through_association.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module Associations
+ module HasOneThroughAssociation
+ extend ActiveSupport::Concern
+
+ def find_target
+ return scope.first if disable_joins
+
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/associations/preloader/through_association.rb b/lib/gem_extensions/active_record/associations/preloader/through_association.rb
new file mode 100644
index 00000000000..16b53846a58
--- /dev/null
+++ b/lib/gem_extensions/active_record/associations/preloader/through_association.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module Associations
+ module Preloader
+ module ThroughAssociation
+ extend ActiveSupport::Concern
+
+ def through_scope
+ scope = through_reflection.klass.unscoped
+ options = reflection.options
+
+ return scope if options[:disable_joins]
+
+ super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/configurable_disable_joins.rb b/lib/gem_extensions/active_record/configurable_disable_joins.rb
new file mode 100644
index 00000000000..8e4c6bd6fc5
--- /dev/null
+++ b/lib/gem_extensions/active_record/configurable_disable_joins.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module ConfigurableDisableJoins
+ extend ActiveSupport::Concern
+
+ def disable_joins
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ return @disable_joins.call if @disable_joins.is_a?(Proc)
+
+ @disable_joins
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/delegate_cache.rb b/lib/gem_extensions/active_record/delegate_cache.rb
new file mode 100644
index 00000000000..63c93f7a2d3
--- /dev/null
+++ b/lib/gem_extensions/active_record/delegate_cache.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module DelegateCache
+ def relation_delegate_class(klass)
+ @relation_delegate_cache2[klass] || super # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
+ def initialize_relation_delegate_cache_disable_joins
+ @relation_delegate_cache2 = {} # rubocop:disable Gitlab/ModuleWithInstanceVariables
+
+ [
+ ::GemExtensions::ActiveRecord::DisableJoins::Relation
+ ].each do |klass|
+ delegate = Class.new(klass) do
+ include ::ActiveRecord::Delegation::ClassSpecificRelation
+ end
+ include_relation_methods(delegate)
+ mangled_name = klass.name.gsub("::", "_")
+ const_set mangled_name, delegate
+ private_constant mangled_name
+
+ @relation_delegate_cache2[klass] = delegate # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+ end
+
+ def inherited(child_class)
+ child_class.initialize_relation_delegate_cache_disable_joins
+ super
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/disable_joins/associations/association_scope.rb b/lib/gem_extensions/active_record/disable_joins/associations/association_scope.rb
new file mode 100644
index 00000000000..1e4476330a2
--- /dev/null
+++ b/lib/gem_extensions/active_record/disable_joins/associations/association_scope.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module DisableJoins
+ module Associations
+ class AssociationScope < ::ActiveRecord::Associations::AssociationScope # :nodoc:
+ def scope(association)
+ source_reflection = association.reflection
+ owner = association.owner
+ unscoped = association.klass.unscoped
+ reverse_chain = get_chain(source_reflection, association, unscoped.alias_tracker).reverse
+
+ previous_reflection, last_reflection, last_ordered, last_join_ids = last_scope_chain(reverse_chain, owner)
+
+ add_constraints(last_reflection, last_reflection.join_primary_key, last_join_ids, owner, last_ordered,
+ previous_reflection: previous_reflection)
+ end
+
+ private
+
+ def last_scope_chain(reverse_chain, owner)
+ # Pulled from https://github.com/rails/rails/pull/42448
+ # Fixes cases where the foreign key is not id
+ first_item = reverse_chain.shift
+ first_scope = [nil, first_item, false, [owner._read_attribute(first_item.join_foreign_key)]]
+
+ reverse_chain.inject(first_scope) do |(previous_reflection, reflection, ordered, join_ids), next_reflection|
+ key = reflection.join_primary_key
+ records = add_constraints(reflection, key, join_ids, owner, ordered, previous_reflection: previous_reflection)
+ foreign_key = next_reflection.join_foreign_key
+ record_ids = records.pluck(foreign_key) # rubocop:disable CodeReuse/ActiveRecord
+ records_ordered = records && records.order_values.any?
+
+ [reflection, next_reflection, records_ordered, record_ids]
+ end
+ end
+
+ def add_constraints(reflection, key, join_ids, owner, ordered, previous_reflection: nil)
+ scope = reflection.build_scope(reflection.aliased_table).where(key => join_ids) # rubocop:disable CodeReuse/ActiveRecord
+
+ # Pulled from https://github.com/rails/rails/pull/42590
+ # Fixes cases where used with an STI type
+ relation = reflection.klass.scope_for_association
+ scope.merge!(
+ relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
+ )
+
+ # Attempt to fix use case where we have a polymorphic relationship
+ # Build on an additional scope to filter by the polymorphic type
+ if reflection.type
+ polymorphic_class = previous_reflection.try(:klass) || owner.class
+
+ polymorphic_type = transform_value(polymorphic_class.polymorphic_name)
+ scope = apply_scope(scope, reflection.aliased_table, reflection.type, polymorphic_type)
+ end
+
+ scope = reflection.constraints.inject(scope) do |memo, scope_chain_item|
+ item = eval_scope(reflection, scope_chain_item, owner)
+ scope.unscope!(*item.unscope_values)
+ scope.where_clause += item.where_clause
+ scope.order_values = item.order_values | scope.order_values
+ scope
+ end
+
+ if scope.order_values.empty? && ordered
+ split_scope = DisableJoins::Relation.create(scope.klass, key, join_ids)
+ split_scope.where_clause += scope.where_clause
+ split_scope
+ else
+ scope
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gem_extensions/active_record/disable_joins/relation.rb b/lib/gem_extensions/active_record/disable_joins/relation.rb
new file mode 100644
index 00000000000..01eb6381a85
--- /dev/null
+++ b/lib/gem_extensions/active_record/disable_joins/relation.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module GemExtensions
+ module ActiveRecord
+ module DisableJoins
+ class Relation < ::ActiveRecord::Relation
+ attr_reader :ids, :key
+
+ def initialize(klass, key, ids)
+ @ids = ids.uniq
+ @key = key
+ super(klass)
+ end
+
+ def limit(value)
+ records.take(value) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
+ def first(limit = nil)
+ if limit
+ records.limit(limit).first
+ else
+ records.first
+ end
+ end
+
+ def load
+ super
+ records = @records
+
+ records_by_id = records.group_by do |record|
+ record[key]
+ end
+
+ records = ids.flat_map { |id| records_by_id[id.to_i] }
+ records.compact!
+
+ @records = records
+ end
+ end
+ end
+ end
+end