activerecord_where_assoc 1.1.5 → 1.2.1

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
  SHA256:
3
- metadata.gz: 366ed8e028f8ed692b13cd8c487385ec380c57f41ce51edcd3370a2412ffe8f3
4
- data.tar.gz: c6d2c1b2111cda267e7c5c6555692317ad0188cd748972f262acbdcba02eee7e
3
+ metadata.gz: ad309caebf6c729645f97a3c76959f0e61557b956fe86fe13dac641af06f87d4
4
+ data.tar.gz: 7f4967c6493d13550f31036753a50606f7e82f9fed1d5277b06db899517e89ec
5
5
  SHA512:
6
- metadata.gz: 1dd9b3955c50ec5ed37ca032b7aa20d93eac2ab035a914e1032efe1527a7b86cd33cd3832c3fa68010bfafef4780f2dfba473e99fa0f089bb3649c8aed578c27
7
- data.tar.gz: 6bae922127f45e21c2484c3ef28f0d47512cb1d2cc0b1e94a97a628277698eceb04595b3f96759d367be1e94e4441ad72e8e2901aabf44169994989579b68d00
6
+ metadata.gz: 0efde09f38bd4c4913ac3b4cb36d114cb654c0c35bd05c07eb31559801d9febb87b479d9ae41b6bc07d9d77ddef775fd3b1e307ea20a3a10ccf2b829f03f27f1
7
+ data.tar.gz: ef3d2dbfef7f58f0221cdf5c32448410a1886fd1b8532bbe2d4a8643d5bde2dad5c2029eeddf5cb1b6620ab199b6ec2e3bd313ddd505d648a90d2d2f619b2747
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Unreleased
2
2
 
3
+ # 1.2.1 - 2024-12-05
4
+
5
+ * Optimize `has_one` handling for `#where_assoc_exists` with a `has_one` as last association + without any conditions or offset.
6
+ * Optimize `has_one` handling when the foreign_key has a unique index and there is no offset
7
+
8
+ # 1.2.0 - 2024-08-31
9
+
10
+ * Add support for composite primary keys in Rails 7.2
11
+
3
12
  # 1.1.5 - 2024-05-18
4
13
 
5
14
  * Add compatibility for Rails 7.2
@@ -22,12 +31,12 @@
22
31
 
23
32
  # 1.1.0 - 2020-02-24
24
33
 
25
- * Added methods which return the SQL used by this gem: `assoc_exists_sql`, `assoc_not_exists_sql`, `compare_assoc_count_sql`, `only_assoc_count_sql`
34
+ * Added methods which return the SQL used by this gem: `assoc_exists_sql`, `assoc_not_exists_sql`, `compare_assoc_count_sql`, `only_assoc_count_sql`
26
35
  [Documentation for them](https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/SqlReturningMethods.html)
27
36
 
28
37
  # 1.0.1
29
38
 
30
- * Fix broken urls in error messages
39
+ * Fix broken urls in error messages
31
40
 
32
41
  # 1.0.0
33
42
 
@@ -35,16 +44,16 @@
35
44
 
36
45
  # 0.1.3
37
46
 
38
- * Use `SELECT 1` instead of `SELECT 0`...
47
+ * Use `SELECT 1` instead of `SELECT 0`...
39
48
  ... it just seems more natural that way.
40
49
  * Bugfixes
41
50
 
42
51
  # 0.1.2
43
52
 
44
- * It is now possible to pass a `Range` as first argument to `#where_assoc_count`.
53
+ * It is now possible to pass a `Range` as first argument to `#where_assoc_count`.
45
54
  Ex: Users that have between 10 and 20 posts
46
55
  `User.where_assoc_count(10..20, :==, :posts)`
47
- The operator in that case must be either :== or :!=.
48
- This will use `BETWEEN` and `NOT BETWEEN`.
56
+ The operator in that case must be either :== or :!=.
57
+ This will use `BETWEEN` and `NOT BETWEEN`.
49
58
  Ranges that exclude the last value, i.e. `5...10`, are also supported, resulting in `BETWEEN 5 and 9`.
50
59
  Ranges with infinities are also supported.
@@ -101,5 +101,28 @@ module ActiveRecordWhereAssoc
101
101
  reflection.is_a?(ActiveRecord::NullRelation)
102
102
  end
103
103
  end
104
+
105
+ if ActiveRecord.gem_version >= Gem::Version.new("6.0")
106
+ def self.indexes(model)
107
+ model.connection.schema_cache.indexes(model.table_name)
108
+ end
109
+ else
110
+ def self.indexes(model)
111
+ model.connection.indexes(model.table_name)
112
+ end
113
+ end
114
+
115
+ @unique_indexes_cache = {}
116
+ def self.has_unique_index?(model, column_names)
117
+ column_names = Array(column_names).map(&:to_s)
118
+ @unique_indexes_cache.fetch([model, column_names]) do |k|
119
+ unique_indexes = indexes(model).select(&:unique)
120
+ columns_names_set = Set.new(column_names)
121
+
122
+ # We check for an index whose columns are a subset of the columns we specify
123
+ # This way, a composite column_names will find uniqueness if just a single of the column is unique
124
+ @unique_indexes_cache[k] = unique_indexes.any? { |ui| Set.new(ui.columns) <= columns_names_set }
125
+ end
126
+ end
104
127
  end
105
128
  end
@@ -155,9 +155,17 @@ module ActiveRecordWhereAssoc
155
155
 
156
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
- current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
159
-
160
158
  if i.zero?
159
+ if given_conditions || klass_scope || last_assoc_block || current_scope.offset_value || nest_assocs_block == NestWithSumBlock
160
+ # In the deepest layer, the limit & offset complexities only matter when:
161
+ # * There is a condition to apply
162
+ # * There is an offset (which is a form of filtering)
163
+ # * We are counting the total matches
164
+ # Since last_assoc_block is always set except for the deepest association, and is only unset for the deepest layer if
165
+ # there is no condition given, using it as part of the condition does a lot of work here.
166
+ current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
167
+ end
168
+
161
169
  current_scope = current_scope.where(given_conditions) if given_conditions
162
170
  if klass_scope
163
171
  if klass_scope.respond_to?(:call)
@@ -167,6 +175,8 @@ module ActiveRecordWhereAssoc
167
175
  end
168
176
  end
169
177
  current_scope = apply_proc_scope(current_scope, last_assoc_block) if last_assoc_block
178
+ else
179
+ current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
170
180
  end
171
181
 
172
182
  # Those make no sense since at this point, we are only limiting the value that would match using conditions
@@ -338,10 +348,18 @@ module ActiveRecordWhereAssoc
338
348
 
339
349
  current_scope = current_scope.limit(1) if reflection.macro == :has_one
340
350
 
341
- # Order is useless without either limit or offset
342
- current_scope = current_scope.unscope(:order) if !current_scope.limit_value && !current_scope.offset_value
351
+ if !current_scope.offset_value
352
+ if current_scope.limit_value
353
+ join_keys = ActiveRecordCompat.join_keys(reflection, nil)
354
+ # #join_keys is inverted... the foreign key is on the "source" table, and the key is on the "target" table...
355
+ # Everything is so complicated in ActiveRecord.
356
+ current_scope = current_scope.unscope(:limit) if ActiveRecordCompat.has_unique_index?(current_scope.model, join_keys.key)
357
+ end
358
+
359
+ # Order is useless without either limit or offset
360
+ return current_scope.unscope(:order) if !current_scope.limit_value
361
+ end
343
362
 
344
- return current_scope unless current_scope.limit_value || current_scope.offset_value
345
363
  if %w(mysql mysql2).include?(relation_klass.connection.adapter_name.downcase)
346
364
  msg = String.new
347
365
  msg << "Associations and default_scopes with a limit or offset are not supported for MySQL (this includes has_many). "
@@ -364,6 +382,9 @@ module ActiveRecordWhereAssoc
364
382
  if reflection.klass.table_name.include?(".") || option_value(options, :never_alias_limit)
365
383
  # We use unscoped to avoid duplicating the conditions in the query, which is noise. (unless it
366
384
  # could helps the query planner of the DB, if someone can show it to be worth it, then this can be changed.)
385
+ if reflection.klass.primary_key.is_a?(Array)
386
+ raise NeverAliasLimitDoesntWorkWithCompositePrimaryKeysError, "Sorry, it just doesn't work..."
387
+ end
367
388
 
368
389
  reflection.klass.unscoped.where(reflection.klass.primary_key.to_sym => current_scope)
369
390
  else
@@ -395,8 +416,13 @@ module ActiveRecordWhereAssoc
395
416
 
396
417
  alias_scope = foreign_klass.base_class.unscoped
397
418
  alias_scope = alias_scope.from("#{table.name} #{ALIAS_TABLE.name}")
398
- alias_scope = alias_scope.where(table[primary_key].eq(ALIAS_TABLE[primary_key]))
399
- alias_scope
419
+
420
+ primary_key_constraints =
421
+ Array(primary_key).map do |a_primary_key|
422
+ table[a_primary_key].eq(ALIAS_TABLE[a_primary_key])
423
+ end
424
+
425
+ alias_scope.where(primary_key_constraints.inject(&:and))
400
426
  end
401
427
 
402
428
  def self.wrapper_and_join_constraints(record_class, reflection, options = {})
@@ -424,7 +450,18 @@ module ActiveRecordWhereAssoc
424
450
  foreign_table = ALIAS_TABLE
425
451
  end
426
452
 
427
- constraints = table[key].eq(foreign_table[foreign_key])
453
+ constraint_keys = Array.wrap(key)
454
+ constraint_foreign_keys = Array.wrap(foreign_key)
455
+ constraint_key_map = constraint_keys.zip(constraint_foreign_keys)
456
+
457
+ primary_foreign_key_constraints =
458
+ constraint_key_map.map do |primary_and_foreign_keys|
459
+ a_primary_key, a_foreign_key = primary_and_foreign_keys
460
+
461
+ table[a_primary_key].eq(foreign_table[a_foreign_key])
462
+ end
463
+
464
+ constraints = primary_foreign_key_constraints.inject(&:and)
428
465
 
429
466
  if reflection.type
430
467
  # Handling of the polymorphic has_many/has_one's type column
@@ -6,4 +6,7 @@ module ActiveRecordWhereAssoc
6
6
 
7
7
  class PolymorphicBelongsToWithoutClasses < StandardError
8
8
  end
9
+
10
+ class NeverAliasLimitDoesntWorkWithCompositePrimaryKeysError < StandardError
11
+ end
9
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecordWhereAssoc
4
- VERSION = "1.1.5".freeze
4
+ VERSION = "1.2.1".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.5
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maxime Handfield Lapointe
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-18 00:00:00.000000000 Z
11
+ date: 2024-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -181,7 +181,7 @@ homepage: https://github.com/MaxLap/activerecord_where_assoc
181
181
  licenses:
182
182
  - MIT
183
183
  metadata: {}
184
- post_install_message:
184
+ post_install_message:
185
185
  rdoc_options: []
186
186
  require_paths:
187
187
  - lib
@@ -196,8 +196,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
196
  - !ruby/object:Gem::Version
197
197
  version: '0'
198
198
  requirements: []
199
- rubygems_version: 3.4.10
200
- signing_key:
199
+ rubygems_version: 3.5.11
200
+ signing_key:
201
201
  specification_version: 4
202
202
  summary: Make ActiveRecord do conditions on your associations
203
203
  test_files: []