activerecord_where_assoc 1.1.2 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c33267f5201fd0465cceec484dd0a2c91b3511b47289f500311e0d3a3946b28f
4
- data.tar.gz: 0b1ef81c3b3106a0226ed72494523ce3a2ebb005f4b1401d6cca3917a78bfa26
3
+ metadata.gz: 4d915a4b0f214283acc2a6ba098018b17fc786f2b801eb2703eba31448b127b9
4
+ data.tar.gz: 614b3c59444a0f6676b63f2499718b16f95ac1d1b010e18e322b10d25be21e1a
5
5
  SHA512:
6
- metadata.gz: 156df4226e93a9d5a8e900a71b518d42ff984a47a8917d7d807429d3b473a5f8db7d392496c8f4dfda7f0746689b9127547d238b2f875846235c9075e4789f7b
7
- data.tar.gz: fbea2c6d875f87c5b04d740fb6878d1f9ece9e11ba0053d38bbe6c8cb6311b194be770ebd402bc59a2baad10c3ecac8ee9f5e2bad8f6147ea152d8e8c2a96518
6
+ metadata.gz: 1baf41c6ae6094045794f1c5a01c4ff3ab1218d9bc027b51e038667a76d210aa7c8819a1eb61404032fb906233400a9da2f7112a94e0610e5b546c1ef7373934
7
+ data.tar.gz: a1fbc573c244b8ea12cf8bd96b0de0ab248bc14ef5eeb5f1503715951e746ccae9e103fa98baee2f666c971496732bd4f938ece8d77cea6770ad1614d3feed43
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.1.4 - 2023-10-10
4
+
5
+ * Add compatibility for Rails 7.1
6
+
7
+ # 1.1.3 - 2022-08-16
8
+
9
+ * Add support for associations defined on abstract models
10
+
11
+ # 1.1.2 - 2020-12-24
12
+
13
+ * Add compatibility for Rails 6.1
14
+
3
15
  # 1.1.1 - 2020-04-13
4
16
 
5
17
  * Fix handling for ActiveRecord's NullRelation (MyModel.none) in block and association's conditions.
data/EXAMPLES.md CHANGED
@@ -1,3 +1,4 @@
1
+ SELECT "users".* FROM "users"
1
2
  Here are some example usages of the gem, along with the generated SQL.
2
3
 
3
4
  Each of those methods can be chained with scoping methods, so they can be used on `Post`, `my_user.posts`, `Post.where('hello')` or inside a scope. Note that for the `*_sql` variants, those should preferably be used on classes only, because otherwise, it could be confusing for a reader.
@@ -107,13 +108,13 @@ User.where_assoc_exists(:posts).or(User.where_assoc_exists(:comments))
107
108
  ```
108
109
  ```sql
109
110
  SELECT "users".* FROM "users"
110
- WHERE ((EXISTS (
111
+ WHERE (EXISTS (
111
112
  SELECT 1 FROM "posts"
112
113
  WHERE "posts"."author_id" = "users"."id"
113
- )) OR (EXISTS (
114
+ ) OR EXISTS (
114
115
  SELECT 1 FROM "comments"
115
116
  WHERE "comments"."author_id" = "users"."id"
116
- )))
117
+ ))
117
118
  ```
118
119
 
119
120
  ---
data/README.md CHANGED
@@ -37,9 +37,8 @@ These methods have many advantages over the alternative ways of achieving the si
37
37
 
38
38
  ## Installation
39
39
 
40
- Rails 4.1 to 6.1 are supported with Ruby 2.1 to 2.7.
41
- Tested against SQLite3, PostgreSQL and MySQL.
42
- The gem only depends on the `activerecord` gem.
40
+ Rails 4.1 to 7.0 are supported with Ruby 2.1 to 3.1. Tested against SQLite3, PostgreSQL and MySQL. The gem
41
+ only depends on the `activerecord` gem.
43
42
 
44
43
  Add this line to your application's Gemfile:
45
44
 
@@ -51,7 +50,7 @@ And then execute:
51
50
 
52
51
  $ bundle install
53
52
 
54
- Or install it yourself as:
53
+ Or install it yourself with:
55
54
 
56
55
  $ gem install activerecord_where_assoc
57
56
 
@@ -235,6 +234,19 @@ It is pretty complicated to support `#limit` and `#offset` of the `has_* :throug
235
234
 
236
235
  Note that the support of `#limit` and `#offset` for the `:source` and `:through` parts is a feature. I consider `ActiveRecord` wrong for not handling them correctly.
237
236
 
237
+ ## Another recommended gem
238
+
239
+ If you feel a need for this gem's feature, you may also be interested in another gem I made: [activerecord_follow_assoc](https://github.com/MaxLap/activerecord_follow_assoc).
240
+
241
+ It allows you to follow an association of your choice while building a query (a scope). You start querying posts, and then you change to querying the authors of those posts. For simple cases, it's possible that both `where_assoc` and `follow_assoc` can build the query your need, but each can handle different situations. Here is an example:
242
+
243
+ ```ruby
244
+ # Find every posts that have comments by an admin
245
+ Post.where_assoc_exists([:comments, :author], &:admins)
246
+ ```
247
+
248
+ This could be done with `follow_assoc`: `User.admins.follow_assoc(:comments, :post)`. But if you wanted conditions on a second association, then `follow_assoc` wouldn't work. On the other hand, if you received a scope on users and wanted their posts, then `follow_assoc` would be a nicer tool for the job. It all depends on the context where you need to do the query and what starting point you have.
249
+
238
250
  ## Development
239
251
 
240
252
  After checking out the repo, run `bundle install` to install dependencies.
@@ -91,5 +91,15 @@ module ActiveRecordWhereAssoc
91
91
  reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
92
92
  end
93
93
  end
94
+
95
+ if ActiveRecord.gem_version >= Gem::Version.new("7.1.0.alpha")
96
+ def self.null_relation?(reflection)
97
+ reflection.null_relation?
98
+ end
99
+ else
100
+ def self.null_relation?(reflection)
101
+ reflection.is_a?(ActiveRecord::NullRelation)
102
+ end
103
+ end
94
104
  end
95
105
  end
@@ -13,7 +13,7 @@ module ActiveRecordWhereAssoc
13
13
  # => "EXISTS (SELECT... *relation1*) OR EXISTS (SELECT... *relation2*)"
14
14
  def self.sql_for_any_exists(relations)
15
15
  relations = [relations] unless relations.is_a?(Array)
16
- relations = relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
16
+ relations = relations.reject { |rel| ActiveRecordCompat.null_relation?(rel) }
17
17
  sqls = relations.map { |rel| "EXISTS (#{rel.select('1').to_sql})" }
18
18
  if sqls.size > 1
19
19
  "(#{sqls.join(" OR ")})" # Parens needed when embedding the sql in a `where`, because the OR could make things wrong
@@ -33,7 +33,7 @@ module ActiveRecordWhereAssoc
33
33
  # => "SUM((SELECT... *relation1*)) + SUM((SELECT... *relation2*))"
34
34
  def self.sql_for_sum_of_counts(relations)
35
35
  relations = [relations] unless relations.is_a?(Array)
36
- relations = relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
36
+ relations = relations.reject { |rel| ActiveRecordCompat.null_relation?(rel) }
37
37
  # Need the double parentheses
38
38
  relations.map { |rel| "SUM((#{rel.to_sql}))" }.join(" + ").presence || "0"
39
39
  end
@@ -85,7 +85,7 @@ module ActiveRecordWhereAssoc
85
85
  end
86
86
 
87
87
  nested_relations = relations_on_association(record_class, association_names, given_conditions, options, deepest_scope_mod, NestWithSumBlock)
88
- nested_relations = nested_relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
88
+ nested_relations = nested_relations.reject { |rel| ActiveRecordCompat.null_relation?(rel) }
89
89
  nested_relations.map { |nr| "COALESCE((#{nr.to_sql}), 0)" }.join(" + ").presence || "0"
90
90
  end
91
91
 
@@ -141,7 +141,7 @@ module ActiveRecordWhereAssoc
141
141
  # Each step, we get all of the scoping lambdas that were defined on associations that apply for
142
142
  # the reflection's target
143
143
  # Basically, we start from the deepest part of the query and wrap it up
144
- reflection_chain, constaints_chain = ActiveRecordCompat.chained_reflection_and_chained_constraints(final_reflection)
144
+ reflection_chain, constraints_chain = ActiveRecordCompat.chained_reflection_and_chained_constraints(final_reflection)
145
145
  skip_next = false
146
146
 
147
147
  reflection_chain.each_with_index do |reflection, i|
@@ -153,7 +153,7 @@ module ActiveRecordWhereAssoc
153
153
  # the 2nd part of has_and_belongs_to_many is handled at the same time as the first.
154
154
  skip_next = true if actually_has_and_belongs_to_many?(reflection)
155
155
 
156
- init_scopes = initial_scopes_from_reflection(reflection_chain[i..-1], constaints_chain[i], options)
156
+ init_scopes = initial_scopes_from_reflection(record_class, reflection_chain[i..-1], constraints_chain[i], options)
157
157
  current_scopes = init_scopes.map do |alias_scope, current_scope, klass_scope|
158
158
  current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
159
159
 
@@ -197,13 +197,13 @@ module ActiveRecordWhereAssoc
197
197
  end
198
198
 
199
199
  # Can return multiple pairs for polymorphic belongs_to, one per table to look into
200
- def self.initial_scopes_from_reflection(reflection_chain, assoc_scopes, options)
200
+ def self.initial_scopes_from_reflection(record_class, reflection_chain, assoc_scopes, options)
201
201
  reflection = reflection_chain.first
202
202
  actual_source_reflection = user_defined_actual_source_reflection(reflection)
203
203
 
204
204
  on_poly_belongs_to = option_value(options, :poly_belongs_to) if poly_belongs_to?(actual_source_reflection)
205
205
 
206
- classes_with_scope = classes_with_scope_for_reflection(reflection, options)
206
+ classes_with_scope = classes_with_scope_for_reflection(record_class, reflection, options)
207
207
 
208
208
  assoc_scope_allowed_lim_off = assoc_scope_to_keep_lim_off_from(reflection)
209
209
 
@@ -225,18 +225,18 @@ module ActiveRecordWhereAssoc
225
225
  # would be great, except we cannot add a given_conditions afterward because we are on the wrong "base class",
226
226
  # and we can't do #merge because of the LEW crap.
227
227
  # So we must do the joins ourself!
228
- _wrapper, sub_join_contraints = wrapper_and_join_constraints(reflection)
228
+ _wrapper, sub_join_contraints = wrapper_and_join_constraints(record_class, reflection)
229
229
  next_reflection = reflection_chain[1]
230
230
 
231
231
  current_scope = current_scope.joins(<<-SQL)
232
232
  INNER JOIN #{next_reflection.klass.quoted_table_name} ON #{sub_join_contraints.to_sql}
233
233
  SQL
234
234
 
235
- alias_scope, join_constaints = wrapper_and_join_constraints(next_reflection, habtm_other_reflection: reflection)
235
+ alias_scope, join_constraints = wrapper_and_join_constraints(record_class, next_reflection, habtm_other_reflection: reflection)
236
236
  elsif on_poly_belongs_to
237
- alias_scope, join_constaints = wrapper_and_join_constraints(reflection, poly_belongs_to_klass: klass)
237
+ alias_scope, join_constraints = wrapper_and_join_constraints(record_class, reflection, poly_belongs_to_klass: klass)
238
238
  else
239
- alias_scope, join_constaints = wrapper_and_join_constraints(reflection)
239
+ alias_scope, join_constraints = wrapper_and_join_constraints(record_class, reflection)
240
240
  end
241
241
 
242
242
  assoc_scopes.each do |callable|
@@ -256,7 +256,7 @@ module ActiveRecordWhereAssoc
256
256
  current_scope = current_scope.merge(relation)
257
257
  end
258
258
 
259
- [alias_scope, current_scope.where(join_constaints), klass_scope]
259
+ [alias_scope, current_scope.where(join_constraints), klass_scope]
260
260
  end
261
261
  end
262
262
 
@@ -272,7 +272,7 @@ module ActiveRecordWhereAssoc
272
272
  user_defined_actual_source_reflection(reflection).scope
273
273
  end
274
274
 
275
- def self.classes_with_scope_for_reflection(reflection, options)
275
+ def self.classes_with_scope_for_reflection(record_class, reflection, options)
276
276
  actual_source_reflection = user_defined_actual_source_reflection(reflection)
277
277
 
278
278
  if poly_belongs_to?(actual_source_reflection)
@@ -283,7 +283,15 @@ module ActiveRecordWhereAssoc
283
283
  else
284
284
  case on_poly_belongs_to
285
285
  when :pluck
286
- class_names = actual_source_reflection.active_record.distinct.pluck(actual_source_reflection.foreign_type)
286
+ model_for_ids = actual_source_reflection.active_record
287
+
288
+ if model_for_ids.abstract_class
289
+ # When the reflection is defined on an abstract model, we fallback to the model
290
+ # on which this was called
291
+ model_for_ids = record_class
292
+ end
293
+
294
+ class_names = model_for_ids.distinct.pluck(actual_source_reflection.foreign_type)
287
295
  class_names.compact.map!(&:safe_constantize).compact
288
296
  when Array, Hash
289
297
  array = on_poly_belongs_to.to_a
@@ -326,7 +334,7 @@ module ActiveRecordWhereAssoc
326
334
  end
327
335
 
328
336
  # No need to do transformations if this is already a NullRelation
329
- return current_scope if current_scope.is_a?(ActiveRecord::NullRelation)
337
+ return current_scope if ActiveRecordCompat.null_relation?(current_scope)
330
338
 
331
339
  current_scope = current_scope.limit(1) if reflection.macro == :has_one
332
340
 
@@ -391,7 +399,7 @@ module ActiveRecordWhereAssoc
391
399
  alias_scope
392
400
  end
393
401
 
394
- def self.wrapper_and_join_constraints(reflection, options = {})
402
+ def self.wrapper_and_join_constraints(record_class, reflection, options = {})
395
403
  poly_belongs_to_klass = options[:poly_belongs_to_klass]
396
404
  join_keys = ActiveRecordCompat.join_keys(reflection, poly_belongs_to_klass)
397
405
 
@@ -400,6 +408,12 @@ module ActiveRecordWhereAssoc
400
408
 
401
409
  table = (poly_belongs_to_klass || reflection.klass).arel_table
402
410
  foreign_klass = reflection.send(:actual_source_reflection).active_record
411
+ if foreign_klass.abstract_class
412
+ # When the reflection is defined on an abstract model, we fallback to the model
413
+ # on which this was called
414
+ foreign_klass = record_class
415
+ end
416
+
403
417
  foreign_table = foreign_klass.arel_table
404
418
 
405
419
  habtm_other_reflection = options[:habtm_other_reflection]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordWhereAssoc
4
- VERSION = "1.1.2".freeze
4
+ VERSION = "1.1.4".freeze
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord_where_assoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maxime Handfield Lapointe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-24 00:00:00.000000000 Z
11
+ date: 2023-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -31,6 +31,9 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.15'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '2.6'
34
37
  type: :development
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
@@ -38,6 +41,9 @@ dependencies:
38
41
  - - ">="
39
42
  - !ruby/object:Gem::Version
40
43
  version: '1.15'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.6'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: minitest
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -190,7 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
190
196
  - !ruby/object:Gem::Version
191
197
  version: '0'
192
198
  requirements: []
193
- rubygems_version: 3.0.3
199
+ rubygems_version: 3.4.10
194
200
  signing_key:
195
201
  specification_version: 4
196
202
  summary: Make ActiveRecord do conditions on your associations