pg_any_where 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a825cb6b120563bf415db84e4796acd7c4d5e370e5bf617d4b034f0721bf315a
4
+ data.tar.gz: 7f3fd236b46cd43e9721bdc8eb276056e3045f9793d6c8356e9c30683e202d65
5
+ SHA512:
6
+ metadata.gz: 97649e2bc9817fed522c8f90e2604489b7f09a8678776caa7ccaad0ef578ab43c463dcb6a2abe051ae2cf9320a1284f35a925d5367644034da0f80c96cce6bfd
7
+ data.tar.gz: '08b888d8b14851f5e4ef839ef9201931078601fdfe773349ea872fb552b8a3651f94f106673dc46f52d2b9468e88fe2add8fad3da10ab384adfdee7294f7cd61'
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-06-26
11
+
12
+ ### Added
13
+ - Initial release.
14
+ - Rewrites `WHERE column IN (…)` to `WHERE column = ANY($1::type[])`.
15
+ - Rewrites `WHERE column NOT IN (…)` to `WHERE column != ALL($1::type[])`.
16
+ - Handles empty-array edge cases: `ANY(ARRAY[]::type[])` / `!= ALL(ARRAY[]::type[])`.
17
+ - Block-style `PgAnyWhere.configure` API.
18
+ - ENV variable support: `PG_ANY_WHERE_ENABLED` and `PG_ANY_WHERE_MIN_ARRAY_SIZE`.
19
+ - `min_array_size` threshold — fall back to IN for small arrays if desired.
20
+ - Rails Railtie for zero-configuration setup.
21
+ - RSpec test suite with SimpleCov coverage enforcement (≥ 95 %).
22
+
23
+ [Unreleased]: https://github.com/aimanabutalaah/pg_any_where/compare/v0.1.0...HEAD
24
+ [0.1.0]: https://github.com/aimanabutalaah/pg_any_where/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aiman Abutalaah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # pg_any_where
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/pg_any_where.svg)](https://rubygems.org/gems/pg_any_where)
4
+ [![CI](https://github.com/aimanabutalaah/pg_any_where/actions/workflows/ci.yml/badge.svg)](https://github.com/aimanabutalaah/pg_any_where/actions)
5
+ [![Coverage](https://img.shields.io/badge/coverage-95%25%2B-brightgreen)]()
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.txt)
7
+
8
+ > **One gem. One bind parameter. Zero plan-cache pollution.**
9
+
10
+ `pg_any_where` patches ActiveRecord so that `where(column: array)` emits
11
+ PostgreSQL's array operators instead of `IN (…)`:
12
+
13
+ ```sql
14
+ -- Before (standard ActiveRecord)
15
+ SELECT * FROM widgets WHERE id IN ($1, $2, $3)
16
+
17
+ -- After (pg_any_where)
18
+ SELECT * FROM widgets WHERE id = ANY($1::integer[])
19
+ ```
20
+
21
+ A 1 000-element array still produces **a single bind parameter**, keeping your
22
+ prepared-statement cache sane and `pg_stat_statements` readable.
23
+
24
+ ---
25
+
26
+ ## Why?
27
+
28
+ PostgreSQL's query planner caches plans keyed on the *shape* of a prepared
29
+ statement, not its bind values. A standard `IN ($1, $2, …)` generates a new
30
+ cache entry for *every distinct array length*, causing:
31
+
32
+ - plan-cache bloat
33
+ - `pg_stat_statements` pollution (thousands of near-identical rows)
34
+ - occasional re-planning overhead on busy servers
35
+
36
+ `= ANY($1::type[])` fixes all three: **one shape, one plan, cached forever**.
37
+
38
+ > Further reading: [rails/rails#49388](https://github.com/rails/rails/pull/49388)
39
+
40
+ ### Empty-array edge cases
41
+
42
+ | Standard ActiveRecord | `pg_any_where` |
43
+ |---|---|
44
+ | `WHERE 1=0` (empty `IN`) | `WHERE col = ANY(ARRAY[]::integer[])` |
45
+ | `WHERE 1=1` (empty `NOT IN`) | `WHERE col != ALL(ARRAY[]::integer[])` |
46
+
47
+ The footguns are gone. Empty arrays behave correctly and symmetrically.
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ Add to your `Gemfile`:
54
+
55
+ ```ruby
56
+ gem "pg_any_where"
57
+ ```
58
+
59
+ Then run:
60
+
61
+ ```sh
62
+ bundle install
63
+ ```
64
+
65
+ ### Requirements
66
+
67
+ | Dependency | Version |
68
+ |---|---|
69
+ | Ruby | ≥ 3.0 |
70
+ | ActiveRecord | ≥ 6.1 |
71
+ | PostgreSQL adapter (`pg`) | ≥ 1.2 |
72
+
73
+ ---
74
+
75
+ ## Usage
76
+
77
+ ### Rails (automatic)
78
+
79
+ The gem ships a **Railtie** — no initializer code required. Simply add it to
80
+ your `Gemfile` and restart your server. Done.
81
+
82
+ To verify it is active:
83
+
84
+ ```ruby
85
+ Widget.where(id: [1, 2, 3]).to_sql
86
+ # => "SELECT ... WHERE \"widgets\".\"id\" = ANY($1::integer[])"
87
+ ```
88
+
89
+ ### Non-Rails
90
+
91
+ ```ruby
92
+ require "pg_any_where"
93
+
94
+ ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])
95
+ PgAnyWhere.patch! # call once after connecting
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Configuration
101
+
102
+ ### Block-style (recommended)
103
+
104
+ ```ruby
105
+ # config/initializers/pg_any_where.rb
106
+ PgAnyWhere.configure do |config|
107
+ # Disable the extension entirely (default: true).
108
+ config.enabled = true
109
+
110
+ # Only rewrite arrays with at least N elements; fall back to IN for smaller
111
+ # ones. Useful if you want IN for 1- or 2-element lists where the planner
112
+ # might prefer an index seek with literal values. (default: 0)
113
+ config.min_array_size = 0
114
+ end
115
+ ```
116
+
117
+ ### Environment variables
118
+
119
+ | Variable | Default | Description |
120
+ |---|---|---|
121
+ | `PG_ANY_WHERE_ENABLED` | `"true"` | Set to `"false"` to disable |
122
+ | `PG_ANY_WHERE_MIN_ARRAY_SIZE` | `"0"` | Minimum array size to rewrite |
123
+
124
+ ENV values act as **defaults** — an explicit Ruby config call always wins.
125
+
126
+ ---
127
+
128
+ ## SQL reference
129
+
130
+ | ActiveRecord | SQL emitted |
131
+ |---|---|
132
+ | `where(col: [1, 2, 3])` | `col = ANY($1::integer[])` |
133
+ | `where(col: [])` | `col = ANY(ARRAY[]::integer[])` |
134
+ | `where.not(col: [1, 2])` | `col != ALL($1::integer[])` |
135
+ | `where.not(col: [])` | `col != ALL(ARRAY[]::integer[])` |
136
+ | `where(str_col: %w[a b])` | `str_col = ANY($1::character varying[])` |
137
+
138
+ ---
139
+
140
+ ## Compatibility notes
141
+
142
+ ### Ransack / raw Arel
143
+
144
+ Ransack and manual `attribute.in([…])` calls build `Arel::Nodes::In` nodes
145
+ whose values are already-quoted `Arel::Nodes::Node` objects. Re-casting those
146
+ would produce an empty array, so **pg_any_where deliberately leaves them
147
+ unchanged**. Your Ransack queries continue to work exactly as before.
148
+
149
+ ### `min_array_size`
150
+
151
+ When `min_array_size > 0`, arrays shorter than the threshold fall back to the
152
+ standard IN / NOT IN behaviour — including the `1=0` / `1=1` edge cases for
153
+ empty arrays.
154
+
155
+ ---
156
+
157
+ ## Contributing
158
+
159
+ 1. Fork the repository.
160
+ 2. Create a feature branch (`git checkout -b feature/my-change`).
161
+ 3. Run the test suite (`bundle exec rspec`).
162
+ 4. Run the linter (`bundle exec rubocop`).
163
+ 5. Open a pull request.
164
+
165
+ ### Running tests locally
166
+
167
+ ```sh
168
+ createdb pg_any_where_test
169
+ DATABASE_URL=postgres://localhost/pg_any_where_test bundle exec rspec
170
+ ```
171
+
172
+ Coverage is enforced at **≥ 95 %** via SimpleCov.
173
+
174
+ ---
175
+
176
+ ## License
177
+
178
+ MIT — see [LICENSE.txt](LICENSE.txt).
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module Visitors
5
+ # Arel visitor extension that rewrites IN / NOT IN predicates for PostgreSQL
6
+ # to use the array operators ANY and ALL.
7
+ #
8
+ # Prepended onto +Arel::Visitors::PostgreSQL+ via +PgAnyWhere.patch!+.
9
+ #
10
+ # SQL shape produced:
11
+ #
12
+ # -- non-empty, inclusive
13
+ # "table"."column" = ANY($1::integer[])
14
+ #
15
+ # -- empty, inclusive (no 1=0 footgun)
16
+ # "table"."column" = ANY(ARRAY[]::integer[])
17
+ #
18
+ # -- non-empty, exclusive
19
+ # "table"."column" != ALL($1::integer[])
20
+ #
21
+ # -- empty, exclusive (no 1=1 footgun)
22
+ # "table"."column" != ALL(ARRAY[]::integer[])
23
+ #
24
+ # rubocop:disable Naming/MethodName -- Arel visitor method names mirror node class names
25
+ module PgAnyWhereVisitor
26
+ private
27
+
28
+ # Rails 6.1+ emits HomogeneousIn for +where(col: [1, 2, 3])+ when all
29
+ # values are of the same Ruby class.
30
+ def visit_Arel_Nodes_HomogeneousIn(o, collector)
31
+ return super unless array_membership_applicable?(o.attribute, o.values)
32
+
33
+ emit_array_membership(
34
+ o.left,
35
+ o.casted_values,
36
+ o.type == :in,
37
+ o.attribute,
38
+ collector
39
+ )
40
+ end
41
+
42
+ # Handles +Arel::Nodes::In+ — including empty-array cases produced by
43
+ # +where(col: [])+ and heterogeneous arrays.
44
+ #
45
+ # NOTE: Ransack (and raw Arel +.in+ calls) build In nodes whose +right+
46
+ # side contains +Arel::Nodes::Node+ children (e.g. Quoted / Casted).
47
+ # Re-casting those is unsafe, so we leave them to the default visitor.
48
+ def visit_Arel_Nodes_In(o, collector)
49
+ attr = o.left
50
+ values = o.right
51
+
52
+ if values.is_a?(Array) && convertible_in_list?(attr, values)
53
+ values.delete_if { |v| unboundable?(v) } unless values.empty?
54
+
55
+ return emit_array_membership(
56
+ attr,
57
+ cast_values_for_attribute(attr, values),
58
+ true,
59
+ attr,
60
+ collector
61
+ )
62
+ end
63
+
64
+ super
65
+ end
66
+
67
+ # Handles +Arel::Nodes::NotIn+ — used by +where.not(col: [1, 2])+.
68
+ def visit_Arel_Nodes_NotIn(o, collector)
69
+ attr = o.left
70
+ values = o.right
71
+
72
+ if values.is_a?(Array) && convertible_in_list?(attr, values)
73
+ values.delete_if { |v| unboundable?(v) } unless values.empty?
74
+
75
+ return emit_array_membership(
76
+ attr,
77
+ cast_values_for_attribute(attr, values),
78
+ false,
79
+ attr,
80
+ collector
81
+ )
82
+ end
83
+
84
+ super
85
+ end
86
+
87
+ # -----------------------------------------------------------------------
88
+ # Predicate helpers
89
+ # -----------------------------------------------------------------------
90
+
91
+ def array_membership_applicable?(attribute, values)
92
+ ::PgAnyWhere.configuration.use_for_values?(values) &&
93
+ attribute.respond_to?(:type_caster)
94
+ end
95
+
96
+ # In nodes that contain Arel::Nodes::Node children (Ransack, raw Arel)
97
+ # must not be converted — re-casting those produces an empty array.
98
+ def convertible_in_list?(attribute, values)
99
+ array_membership_applicable?(attribute, values) &&
100
+ (values.empty? || values.none? { |v| v.is_a?(Arel::Nodes::Node) })
101
+ end
102
+
103
+ # -----------------------------------------------------------------------
104
+ # SQL emission
105
+ # -----------------------------------------------------------------------
106
+
107
+ def emit_array_membership(attribute, values, inclusive, bind_attribute, collector)
108
+ collector.preparable = false
109
+
110
+ visit(attribute, collector)
111
+ collector << (inclusive ? " = ANY(" : " != ALL(")
112
+ append_array_operand(bind_attribute, values, collector)
113
+ collector << ")"
114
+
115
+ collector
116
+ end
117
+
118
+ def append_array_operand(attribute, values, collector)
119
+ array_type = pg_oid_array_type(attribute)
120
+
121
+ if values.empty?
122
+ collector << "ARRAY[]::#{pg_array_sql_type(array_type)}"
123
+ else
124
+ array_data = array_type.serialize(values)
125
+ collector.add_binds(
126
+ [array_data],
127
+ bind_proc_for(attribute, array_type),
128
+ &bind_block
129
+ )
130
+ end
131
+ end
132
+
133
+ # -----------------------------------------------------------------------
134
+ # Type casting
135
+ # -----------------------------------------------------------------------
136
+
137
+ def cast_values_for_attribute(attribute, values)
138
+ return [] if values.empty?
139
+
140
+ type_caster = attribute.type_caster
141
+ values.filter_map do |raw|
142
+ type_caster.serialize(raw) if type_caster.serializable?(raw)
143
+ end
144
+ end
145
+
146
+ # -----------------------------------------------------------------------
147
+ # PostgreSQL OID helpers
148
+ # -----------------------------------------------------------------------
149
+
150
+ def pg_oid_array_type(attribute)
151
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(
152
+ attribute.type_caster
153
+ )
154
+ end
155
+
156
+ def pg_array_sql_type(array_type)
157
+ "#{array_type.type}[]"
158
+ end
159
+
160
+ def bind_proc_for(attribute, array_type)
161
+ lambda do |value|
162
+ ActiveModel::Attribute.with_cast_value(attribute.name, value, array_type)
163
+ end
164
+ end
165
+ end
166
+ # rubocop:enable Naming/MethodName
167
+ end
168
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgAnyWhere
4
+ # Configuration for the PgAnyWhere gem.
5
+ #
6
+ # Set options via the block form:
7
+ #
8
+ # PgAnyWhere.configure do |config|
9
+ # config.enabled = true
10
+ # config.min_array_size = 2
11
+ # end
12
+ #
13
+ # Or via environment variables (ENV values act as defaults when the attribute
14
+ # has not been explicitly set):
15
+ #
16
+ # PG_ANY_WHERE_ENABLED = "true" | "false" (default: "true")
17
+ # PG_ANY_WHERE_MIN_ARRAY_SIZE = integer (default: "0")
18
+ #
19
+ class Configuration
20
+ # @return [Boolean, nil]
21
+ attr_writer :enabled
22
+
23
+ # @return [Integer, nil]
24
+ attr_writer :min_array_size
25
+
26
+ # Whether the extension is active.
27
+ #
28
+ # Explicit Ruby config takes precedence over the environment variable.
29
+ #
30
+ # @return [Boolean]
31
+ def enabled?
32
+ if @enabled.nil?
33
+ ENV.fetch("PG_ANY_WHERE_ENABLED", "true") == "true"
34
+ else
35
+ @enabled
36
+ end
37
+ end
38
+
39
+ # Minimum array size required before the ANY / ALL rewrite is applied.
40
+ # Arrays smaller than this threshold fall back to the standard IN / NOT IN.
41
+ #
42
+ # @return [Integer]
43
+ def min_array_size
44
+ @min_array_size || ENV.fetch("PG_ANY_WHERE_MIN_ARRAY_SIZE", "0").to_i
45
+ end
46
+
47
+ # Returns true when the rewrite should be applied to +values+.
48
+ #
49
+ # @param values [Object]
50
+ # @return [Boolean]
51
+ def use_for_values?(values)
52
+ enabled? &&
53
+ values.is_a?(Array) &&
54
+ values.size >= min_array_size
55
+ end
56
+
57
+ # Reset all settings back to defaults (reads ENV again).
58
+ def reset!
59
+ @enabled = nil
60
+ @min_array_size = nil
61
+ self
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgAnyWhere
4
+ # Rails Railtie — automatically calls +PgAnyWhere.patch!+ during
5
+ # the +after_initialize+ phase so the patch lands after the database
6
+ # connection is established and the Arel visitor class is fully loaded.
7
+ class Railtie < Rails::Railtie
8
+ initializer "pg_any_where.patch_arel_visitor", after: :initialize_database do
9
+ PgAnyWhere.patch!
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgAnyWhere
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "pg_any_where/version"
5
+ require "pg_any_where/configuration"
6
+ require "pg_any_where/arel_visitor"
7
+
8
+ # PgAnyWhere rewrites ActiveRecord array-where conditions to use PostgreSQL's
9
+ # ANY / ALL operators instead of IN / NOT IN.
10
+ #
11
+ # == Quick start (non-Rails)
12
+ #
13
+ # require "pg_any_where"
14
+ # PgAnyWhere.patch!
15
+ #
16
+ # == Configuration
17
+ #
18
+ # PgAnyWhere.configure do |config|
19
+ # config.enabled = true # or set ENV["PG_ANY_WHERE_ENABLED"]
20
+ # config.min_array_size = 2 # or set ENV["PG_ANY_WHERE_MIN_ARRAY_SIZE"]
21
+ # end
22
+ #
23
+ # == Rails
24
+ #
25
+ # No manual setup needed — the Railtie handles everything automatically.
26
+ #
27
+ module PgAnyWhere
28
+ class Error < StandardError; end
29
+
30
+ class << self
31
+ # Returns the global +Configuration+ instance.
32
+ #
33
+ # @return [PgAnyWhere::Configuration]
34
+ def configuration
35
+ @configuration ||= Configuration.new
36
+ end
37
+
38
+ # Yields the global +Configuration+ for block-style setup.
39
+ #
40
+ # @yieldparam config [PgAnyWhere::Configuration]
41
+ def configure
42
+ yield configuration
43
+ end
44
+
45
+ # Patches +Arel::Visitors::PostgreSQL+ with the ANY / ALL rewrite logic.
46
+ #
47
+ # Idempotent — safe to call multiple times.
48
+ #
49
+ # @raise [PgAnyWhere::Error] if +Arel::Visitors::PostgreSQL+ is not defined
50
+ def patch!
51
+ return if @patched
52
+
53
+ unless defined?(Arel::Visitors::PostgreSQL)
54
+ raise Error, "Arel::Visitors::PostgreSQL is not defined. " \
55
+ "Ensure activerecord-postgresql-adapter is loaded before calling PgAnyWhere.patch!"
56
+ end
57
+
58
+ Arel::Visitors::PostgreSQL.prepend(Arel::Visitors::PgAnyWhereVisitor)
59
+ @patched = true
60
+ end
61
+
62
+ # Returns +true+ after +patch!+ has been called successfully.
63
+ #
64
+ # @return [Boolean]
65
+ def patched?
66
+ @patched == true
67
+ end
68
+
69
+ # Shorthand for +PgAnyWhere.configuration.enabled?+.
70
+ #
71
+ # @return [Boolean]
72
+ def enabled?
73
+ configuration.enabled?
74
+ end
75
+
76
+ # Resets all configuration and the patched flag.
77
+ # Primarily used in tests.
78
+ #
79
+ # @return [void]
80
+ def reset!
81
+ @configuration = nil
82
+ # NOTE: we cannot un-prepend a module, so @patched is intentionally NOT
83
+ # reset here — calling patch! again would attempt a second prepend.
84
+ self
85
+ end
86
+ end
87
+ end
88
+
89
+ require "pg_any_where/railtie" if defined?(Rails::Railtie)
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pg_any_where
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Aiman Abutalaah
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.60'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.60'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.26'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.26'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.22'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.22'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov-console
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: database_cleaner-active_record
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.1'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.1'
139
+ description: |
140
+ pg_any_where patches Arel so that `where(column: array)` emits
141
+ `column = ANY($1::type[])` instead of `column IN ($1, $2, …)`.
142
+
143
+ This yields a stable prepared-statement shape (one bind parameter regardless
144
+ of array size), better PostgreSQL plan cache utilisation, and cleaner
145
+ pg_stat_statements output. Empty arrays are handled correctly without the
146
+ `1=0` / `1=1` footguns.
147
+ email: []
148
+ executables: []
149
+ extensions: []
150
+ extra_rdoc_files: []
151
+ files:
152
+ - CHANGELOG.md
153
+ - LICENSE.txt
154
+ - README.md
155
+ - lib/pg_any_where.rb
156
+ - lib/pg_any_where/arel_visitor.rb
157
+ - lib/pg_any_where/configuration.rb
158
+ - lib/pg_any_where/railtie.rb
159
+ - lib/pg_any_where/version.rb
160
+ homepage: https://github.com/aimanabutalaah/pg_any_where
161
+ licenses:
162
+ - MIT
163
+ metadata:
164
+ homepage_uri: https://github.com/aimanabutalaah/pg_any_where
165
+ source_code_uri: https://github.com/aimanabutalaah/pg_any_where
166
+ changelog_uri: https://github.com/aimanabutalaah/pg_any_where/blob/main/CHANGELOG.md
167
+ bug_tracker_uri: https://github.com/aimanabutalaah/pg_any_where/issues
168
+ rubygems_mfa_required: 'true'
169
+ post_install_message:
170
+ rdoc_options: []
171
+ require_paths:
172
+ - lib
173
+ required_ruby_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: 3.0.0
178
+ required_rubygems_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ requirements: []
184
+ rubygems_version: 3.4.21
185
+ signing_key:
186
+ specification_version: 4
187
+ summary: Rewrite ActiveRecord array-where to PostgreSQL ANY / ALL operators
188
+ test_files: []