goldiloader 0.0.9 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5439cf46ad38cb53ae77b4b82480215d59ef665b
4
- data.tar.gz: ef928602e0934eb6a372fb0476e9f807af0c4713
3
+ metadata.gz: c7414c0de13b4e9b5c87d8106cedfdb2028a3640
4
+ data.tar.gz: 2a0650488a2ebdc94d665fa12b3dc7d88282c791
5
5
  SHA512:
6
- metadata.gz: 6f621c060cec10acadf3beca30afbc80442b22734873acb32c3430451f0b83a0d137760ad908ac56f6e74abde5bfad02ba9d994b4e5aa9b4c2425e29f8a905ce
7
- data.tar.gz: 43e6573c8978dd21fced0f8848421a2ec2eaeeffdebda8b88625557477044a6a03deed854fcdedfdeaef8b3c978c5565aa597f37e0367485cb3de29c7e6639fe
6
+ metadata.gz: 46d11df02dbae98f7915b06df38937ba93937671433403ff1f161def9568b92cb1a310eb55eed6c9edb073e5851660bb890d7c324cafca12c99585d269ebd0e9
7
+ data.tar.gz: '09179806ac8786f88f059d0c3cba83c4a85acd7f9fe1794dca06ac6672da948b854264263c709911425689493c2932d7f242504a2b0d0d5f9818d0487b58da17'
@@ -1,11 +1,15 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Goldiloader
4
- module AutoIncludableModel
4
+ module BasePatch
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
8
  attr_writer :auto_include_context
9
+
10
+ class << self
11
+ delegate :auto_include, to: :all
12
+ end
9
13
  end
10
14
 
11
15
  def initialize_copy(other)
@@ -22,131 +26,159 @@ module Goldiloader
22
26
  super
23
27
  end
24
28
  end
25
- end
26
-
27
- ActiveRecord::Base.send(:include, Goldiloader::AutoIncludableModel)
29
+ ::ActiveRecord::Base.include(::Goldiloader::BasePatch)
28
30
 
29
- ActiveRecord::Relation.class_eval do
31
+ module RelationPatch
32
+ def exec_queries
33
+ return super if loaded? || !auto_include_value
30
34
 
31
- def exec_queries_with_auto_include
32
- return exec_queries_without_auto_include if loaded?
33
-
34
- models = exec_queries_without_auto_include
35
- Goldiloader::AutoIncludeContext.register_models(models, eager_load_values)
36
- models
37
- end
35
+ models = super
36
+ Goldiloader::AutoIncludeContext.register_models(models, eager_load_values)
37
+ models
38
+ end
38
39
 
39
- alias_method_chain :exec_queries, :auto_include
40
- end
40
+ def auto_include(auto_include = true)
41
+ spawn.auto_include!(auto_include)
42
+ end
41
43
 
42
- ActiveRecord::Associations::Association.class_eval do
44
+ def auto_include!(auto_include = true)
45
+ self.auto_include_value = auto_include
46
+ self
47
+ end
43
48
 
44
- class_attribute :default_auto_include, :default_fully_load
45
- self.default_auto_include = true
46
- self.default_fully_load = false
49
+ def auto_include_value
50
+ # Note: Don't use get_value because that doesn't work properly with defaulting boolean values
51
+ @values.fetch(:auto_include, true)
52
+ end
47
53
 
48
- def auto_include?
49
- # We only auto include associations that don't have in-memory changes since the
50
- # Rails association Preloader clobbers any in-memory changes
51
- !loaded? && target.blank? && options.fetch(:auto_include) { self.class.default_auto_include } && eager_loadable?
54
+ def auto_include_value=(value)
55
+ if Goldiloader::Compatibility.rails_4?
56
+ raise ::ActiveRecord::Relation::ImmutableRelation if @loaded
57
+ check_cached_relation
58
+ @values[:auto_include] = value
59
+ elsif Goldiloader::Compatibility.rails_5_0?
60
+ assert_mutability!
61
+ @values[:auto_include] = value
62
+ else
63
+ set_value(:auto_include, value)
64
+ end
65
+ end
52
66
  end
67
+ ::ActiveRecord::Relation.prepend(::Goldiloader::RelationPatch)
68
+
69
+ module MergerPatch
70
+ private
53
71
 
54
- def fully_load?
55
- !loaded? && options.fetch(:fully_load) { self.class.default_fully_load }
72
+ def merge_single_values
73
+ relation.auto_include_value = other.auto_include_value
74
+ super
75
+ end
56
76
  end
77
+ ActiveRecord::Relation::Merger.prepend(::Goldiloader::MergerPatch)
57
78
 
58
- private
79
+ module AssociationPatch
80
+ extend ActiveSupport::Concern
59
81
 
60
- def eager_loadable?
61
- association_info = Goldiloader::AssociationInfo.new(self)
62
- !association_info.limit? &&
63
- !association_info.offset? &&
64
- !association_info.group? &&
65
- !association_info.from? &&
66
- !association_info.finder_sql? &&
67
- # Joins not properly eager loaded - See https://github.com/salsify/goldiloader/issues/11
68
- !association_info.joins? &&
69
- # Unscope not properly eager loaded - See https://github.com/salsify/goldiloader/issues/13
70
- !association_info.unscope? &&
71
- !association_info.instance_dependent?
72
- end
82
+ included do
83
+ class_attribute :default_fully_load
84
+ self.default_fully_load = false
85
+ end
73
86
 
74
- def load_with_auto_include(load_method, *args)
75
- if loaded? && !stale_target?
76
- target
77
- elsif auto_include?
78
- Goldiloader::AssociationLoader.load(owner, reflection.name)
79
- target
80
- else
81
- send("#{load_method}_without_auto_include", *args)
87
+ def auto_include?
88
+ # We only auto include associations that don't have in-memory changes since the
89
+ # Rails association Preloader clobbers any in-memory changes
90
+ !loaded? && target.blank? && eager_loadable?
82
91
  end
83
- end
84
92
 
85
- end
93
+ def fully_load?
94
+ !loaded? && options.fetch(:fully_load) { self.class.default_fully_load }
95
+ end
86
96
 
87
- ActiveRecord::Associations::SingularAssociation.class_eval do
97
+ private
88
98
 
89
- private
99
+ def eager_loadable?
100
+ association_info = Goldiloader::AssociationInfo.new(self)
101
+ !association_info.limit? &&
102
+ !association_info.offset? &&
103
+ (!association_info.has_one? || !association_info.order?) &&
104
+ (!association_info.group? || ::Goldiloader::Compatibility.group_eager_loadable?) &&
105
+ (!association_info.from? || ::Goldiloader::Compatibility.from_eager_loadable?) &&
106
+ !association_info.instance_dependent? &&
107
+ association_info.auto_include? &&
108
+ (!owner.destroyed? || ::Goldiloader::Compatibility.destroyed_model_associations_eager_loadable?)
109
+ end
90
110
 
91
- def find_target_with_auto_include(*args)
92
- load_with_auto_include(:find_target, *args)
111
+ def load_with_auto_include
112
+ if loaded? && !stale_target?
113
+ target
114
+ elsif auto_include?
115
+ Goldiloader::AssociationLoader.load(owner, reflection.name)
116
+ target
117
+ else
118
+ yield
119
+ end
120
+ end
93
121
  end
122
+ ::ActiveRecord::Associations::Association.include(::Goldiloader::AssociationPatch)
94
123
 
95
- alias_method_chain :find_target, :auto_include
96
- end
124
+ module SingularAssociationPatch
125
+ private
97
126
 
98
- ActiveRecord::Associations::CollectionAssociation.class_eval do
99
- # Force these methods to load the entire association for fully_load associations
100
- [:first, :second, :third, :fourth, :fifth, :last, :size, :ids_reader, :empty?].each do |method|
101
- # Some of these methods were added in Rails 4
102
- next unless method_defined?(method)
103
-
104
- aliased_target, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
105
- define_method("#{aliased_target}_with_fully_load#{punctuation}") do |*args, &block|
106
- load_target if fully_load?
107
- send("#{aliased_target}_without_fully_load#{punctuation}", *args, &block)
127
+ def find_target(*args)
128
+ load_with_auto_include { super }
108
129
  end
109
-
110
- alias_method_chain method, :fully_load
111
130
  end
131
+ ::ActiveRecord::Associations::SingularAssociation.prepend(::Goldiloader::SingularAssociationPatch)
112
132
 
113
- private
133
+ module CollectionAssociationPatch
134
+ # Force these methods to load the entire association for fully_load associations
135
+ association_methods = [:size, :ids_reader, :empty?]
136
+ if Goldiloader::Compatibility::ACTIVE_RECORD_VERSION < ::Gem::Version.new('5.1')
137
+ association_methods.concat([:first, :second, :third, :fourth, :fifth, :last])
138
+ end
114
139
 
115
- def load_target_with_auto_include(*args)
116
- load_with_auto_include(:load_target, *args)
117
- end
140
+ association_methods.each do |method|
141
+ define_method(method) do |*args, &block|
142
+ load_target if fully_load?
143
+ super(*args, &block)
144
+ end
145
+ end
118
146
 
119
- alias_method_chain :load_target, :auto_include
147
+ def load_target(*args)
148
+ load_with_auto_include { super }
149
+ end
120
150
 
121
- end
151
+ if Goldiloader::Compatibility::ACTIVE_RECORD_VERSION >= ::Gem::Version.new('5.1')
152
+ def find_from_target?
153
+ fully_load? || super
154
+ end
155
+ end
156
+ end
157
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(::Goldiloader::CollectionAssociationPatch)
122
158
 
123
- [ActiveRecord::Associations::HasManyThroughAssociation, ActiveRecord::Associations::HasOneThroughAssociation].each do |klass|
124
- klass.class_eval do
159
+ module ThroughAssociationPatch
125
160
  def auto_include?
126
161
  # Only auto include through associations if the target association is auto-loadable
127
162
  through_association = owner.association(through_reflection.name)
128
163
  through_association.auto_include? && super
129
164
  end
130
165
  end
131
- end
132
-
133
- # uniq in Rails 3 not properly eager loaded - See https://github.com/salsify/goldiloader/issues/16
134
- if ActiveRecord::VERSION::MAJOR < 4
135
- ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
136
- def eager_loadable?
137
- association_info = Goldiloader::AssociationInfo.new(self)
138
- super && !association_info.uniq?
166
+ ::ActiveRecord::Associations::HasManyThroughAssociation.prepend(::Goldiloader::ThroughAssociationPatch)
167
+ ::ActiveRecord::Associations::HasOneThroughAssociation.prepend(::Goldiloader::ThroughAssociationPatch)
168
+
169
+ module CollectionProxyPatch
170
+ # The CollectionProxy just forwards exists? to the underlying scope so we need to intercept this and
171
+ # force it to use size which handles fully_load properly.
172
+ def exists?(*args)
173
+ # We don't fully_load the association when arguments are passed to exists? since Rails always
174
+ # pushes this query into the database without any caching (and it likely not a common
175
+ # scenario worth optimizing).
176
+ if args.empty? && @association.fully_load?
177
+ size > 0
178
+ else
179
+ scope.exists?(*args)
180
+ end
139
181
  end
140
182
  end
141
- end
142
-
143
- # The CollectionProxy just forwards exists? to the underlying scope so we need to intercept this and
144
- # force it to use size which handles fully_load properly.
145
- ActiveRecord::Associations::CollectionProxy.class_eval do
146
- def exists?(*args)
147
- # We don't fully_load the association when arguments are passed to exists? since Rails always
148
- # pushes this query into the database without any caching (and it likely not a common
149
- # scenario worth optimizing).
150
- args.empty? && @association.fully_load? ? size > 0 : super
151
- end
183
+ ::ActiveRecord::Associations::CollectionProxy.prepend(::Goldiloader::CollectionProxyPatch)
152
184
  end
@@ -7,109 +7,42 @@ module Goldiloader
7
7
  @association = association
8
8
  end
9
9
 
10
- def finder_sql?
11
- Goldiloader::Compatibility.association_finder_sql_enabled? &&
12
- association_options[:finder_sql].present?
13
- end
10
+ delegate :association_scope, :reflection, to: :@association
14
11
 
15
- if ActiveRecord::VERSION::MAJOR >= 4
16
- delegate :association_scope, :reflection, to: :@association
12
+ def has_one?
13
+ reflection.has_one?
14
+ end
17
15
 
18
- def read_only?
19
- association_scope && association_scope.readonly_value.present?
20
- end
16
+ def offset?
17
+ association_scope && association_scope.offset_value.present?
18
+ end
21
19
 
22
- def offset?
23
- association_scope && association_scope.offset_value.present?
24
- end
20
+ def limit?
21
+ association_scope && association_scope.limit_value.present?
22
+ end
25
23
 
26
- def limit?
27
- association_scope && association_scope.limit_value.present?
28
- end
24
+ def auto_include?
25
+ association_scope.nil? || association_scope.auto_include_value
26
+ end
29
27
 
30
- def from?
28
+ def from?
29
+ if ActiveRecord::VERSION::MAJOR >= 5
30
+ association_scope && association_scope.from_clause.present?
31
+ else
31
32
  association_scope && association_scope.from_value.present?
32
33
  end
34
+ end
33
35
 
34
- def group?
35
- association_scope && association_scope.group_values.present?
36
- end
37
-
38
- def joins?
39
- # Yuck - Through associations will always have a join for *each* 'through' table
40
- association_scope && (association_scope.joins_values.size - num_through_joins) > 0
41
- end
42
-
43
- def uniq?
44
- association_scope && association_scope.uniq_value
45
- end
46
-
47
- def instance_dependent?
48
- reflection.scope.present? && reflection.scope.arity > 0
49
- end
50
-
51
- def unscope?
52
- Goldiloader::Compatibility.unscope_query_method_enabled? &&
53
- association_scope &&
54
- association_scope.unscope_values.present?
55
- end
56
-
57
- private
58
-
59
- def num_through_joins
60
- association = @association
61
- count = 0
62
- while association.is_a?(ActiveRecord::Associations::ThroughAssociation)
63
- count += 1
64
- association = association.owner.association(association.through_reflection.name)
65
- end
66
- count
67
- end
68
- else
69
- def read_only?
70
- association_options[:readonly].present?
71
- end
72
-
73
- def offset?
74
- association_options[:offset].present?
75
- end
76
-
77
- def limit?
78
- association_options[:limit].present?
79
- end
80
-
81
- def from?
82
- false
83
- end
84
-
85
- def group?
86
- association_options[:group].present?
87
- end
88
-
89
- def joins?
90
- # Rails 3 didn't support joins for associations
91
- false
92
- end
93
-
94
- def uniq?
95
- association_options[:uniq]
96
- end
97
-
98
- def instance_dependent?
99
- # Rails 3 didn't support this
100
- false
101
- end
102
-
103
- def unscope?
104
- # Rails 3 didn't support this
105
- false
106
- end
36
+ def group?
37
+ association_scope && association_scope.group_values.present?
107
38
  end
108
39
 
109
- private
40
+ def order?
41
+ association_scope && association_scope.order_values.present?
42
+ end
110
43
 
111
- def association_options
112
- @association.options
44
+ def instance_dependent?
45
+ reflection.scope.present? && reflection.scope.arity > 0
113
46
  end
114
47
  end
115
48
  end
@@ -10,36 +10,12 @@ module Goldiloader
10
10
  end
11
11
 
12
12
  eager_load(models, association_name)
13
-
14
- # Workaround Rails #15853 by setting models read only
15
- if read_only?(models, association_name)
16
- associated_models = associated_models(models, association_name)
17
- mark_read_only(associated_models)
18
- end
19
13
  end
20
14
 
21
15
  private
22
16
 
23
17
  def eager_load(models, association_name)
24
- if Gem::Version.new(::ActiveRecord::VERSION::STRING) >= Gem::Version.new('4.1')
25
- ::ActiveRecord::Associations::Preloader.new.preload(models, [association_name])
26
- else
27
- ::ActiveRecord::Associations::Preloader.new(models, [association_name]).run
28
- end
29
- end
30
-
31
- def mark_read_only(models)
32
- models.each(&:readonly!)
33
- end
34
-
35
- def read_only?(models, association_name)
36
- model = first_model_with_association(models, association_name)
37
- if model.nil?
38
- false
39
- else
40
- association_info = AssociationInfo.new(model.association(association_name))
41
- association_info.read_only?
42
- end
18
+ ::ActiveRecord::Associations::Preloader.new.preload(models, [association_name])
43
19
  end
44
20
 
45
21
  def first_model_with_association(models, association_name)
@@ -4,33 +4,28 @@ module Goldiloader
4
4
  module AssociationOptions
5
5
  extend self
6
6
 
7
- OPTIONS = [:auto_include, :fully_load]
7
+ OPTIONS = [:fully_load].freeze
8
+
9
+ # This is only used in Rails 5+
10
+ module AssociationBuilderExtension
11
+ def self.build(model, reflection)
12
+ # We have no callbacks to register
13
+ end
14
+
15
+ def self.valid_options
16
+ OPTIONS
17
+ end
18
+ end
8
19
 
9
20
  def register
10
- if ::ActiveRecord::VERSION::MAJOR >= 4
11
- ActiveRecord::Associations::Builder::Association.valid_options.concat(OPTIONS)
21
+ if ::ActiveRecord::VERSION::MAJOR >= 5
22
+ ActiveRecord::Associations::Builder::Association.extensions << AssociationBuilderExtension
12
23
  else
13
- # Each subclass of CollectionAssociation will have its own copy of valid_options so we need
14
- # to register the valid option for each one.
15
- collection_association_classes.each do |assoc_class|
16
- assoc_class.valid_options.concat(OPTIONS)
17
- end
24
+ ActiveRecord::Associations::Builder::Association.valid_options.concat(OPTIONS)
18
25
  end
19
26
  end
20
27
 
21
28
  private
22
-
23
- def collection_association_classes
24
- # Association.descendants doesn't work well with lazy classloading :(
25
- [
26
- ActiveRecord::Associations::Builder::Association,
27
- ActiveRecord::Associations::Builder::BelongsTo,
28
- ActiveRecord::Associations::Builder::HasAndBelongsToMany,
29
- ActiveRecord::Associations::Builder::HasMany,
30
- ActiveRecord::Associations::Builder::HasOne,
31
- ActiveRecord::Associations::Builder::SingularAssociation
32
- ]
33
- end
34
29
  end
35
30
  end
36
31
 
@@ -2,21 +2,33 @@
2
2
 
3
3
  module Goldiloader
4
4
  module Compatibility
5
+ ACTIVE_RECORD_VERSION = ::Gem::Version.new(::ActiveRecord::VERSION::STRING)
6
+ PRE_RAILS_5_2 = ACTIVE_RECORD_VERSION < ::Gem::Version.new('5.2.0')
7
+ POST_RAILS_5_1_4 = ACTIVE_RECORD_VERSION > ::Gem::Version.new('5.1.5')
8
+ PRE_RAILS_5_1_5 = ACTIVE_RECORD_VERSION < ::Gem::Version.new('5.1.5')
9
+ FROM_EAGER_LOADABLE = ACTIVE_RECORD_VERSION >= ::Gem::Version.new('5.1.5') ||
10
+ (ACTIVE_RECORD_VERSION >= ::Gem::Version.new('5.0.7') && ACTIVE_RECORD_VERSION < ::Gem::Version.new('5.1.0'))
11
+ GROUP_EAGER_LOADABLE = FROM_EAGER_LOADABLE
5
12
 
6
- MASS_ASSIGNMENT_SECURITY = ::ActiveRecord::VERSION::MAJOR < 4 || defined?(::ActiveRecord::MassAssignmentSecurity)
7
- ASSOCIATION_FINDER_SQL = ::Gem::Version.new(::ActiveRecord::VERSION::STRING) < ::Gem::Version.new('4.1')
8
- UNSCOPE_QUERY_METHOD = ::Gem::Version.new(::ActiveRecord::VERSION::STRING) >= ::Gem::Version.new('4.1')
13
+ def self.rails_4?
14
+ ::ActiveRecord::VERSION::MAJOR == 4
15
+ end
16
+
17
+ def self.rails_5_0?
18
+ ::ActiveRecord::VERSION::MAJOR == 5 && ::ActiveRecord::VERSION::MINOR == 0
19
+ end
9
20
 
10
- def self.mass_assignment_security_enabled?
11
- MASS_ASSIGNMENT_SECURITY
21
+ # See https://github.com/rails/rails/pull/32375
22
+ def self.destroyed_model_associations_eager_loadable?
23
+ PRE_RAILS_5_2
12
24
  end
13
25
 
14
- def self.association_finder_sql_enabled?
15
- ASSOCIATION_FINDER_SQL
26
+ def self.from_eager_loadable?
27
+ FROM_EAGER_LOADABLE
16
28
  end
17
29
 
18
- def self.unscope_query_method_enabled?
19
- UNSCOPE_QUERY_METHOD
30
+ def self.group_eager_loadable?
31
+ GROUP_EAGER_LOADABLE
20
32
  end
21
33
  end
22
34
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Goldiloader
4
- VERSION = '0.0.9'
4
+ VERSION = '2.1.2'.freeze
5
5
  end