associate_jsonb 0.0.4 → 0.0.5

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: 5cb22feba951a6241ccada849b6d17ae06a4a84c797a49ae139844917d50474c
4
- data.tar.gz: 910b39ddc0f89d8525439d9d4e74244a62bbf3f4d07cb333306879312588dfda
3
+ metadata.gz: 2488f59e2d11f0055e7bf0f619397eb949517e61110d8e5b5a81409231f6f6fe
4
+ data.tar.gz: dbebbe5383f20bdb95e359210df22161eecf1819d0148bede5e587b4a62b3f7b
5
5
  SHA512:
6
- metadata.gz: 5382c87315fbbbc8bc52ebb5efb605e60373c7f24e9d681a008f5d5c53dc26649df89f7233ca1b3b6962fdd16a891c9072e55577a3c40e655faa54f3ec0dc375
7
- data.tar.gz: 3570c685cde53a273f70f5d434d1daacbc32b260b0b0500a3e6ef801b18a50fdd007a1970ede0d692a5eee89a87eba46b73ced15019514cfafece65b01c588a8
6
+ metadata.gz: db5297d3d3557fef9ca21a7aceaebc13788006e3e1597156cfc108308ce902af258435b9c820bad6a9df88735f8f778774ad4fe198e6189067ae223dd0cf4ab4
7
+ data.tar.gz: 8bf8a3a056de12214579f8ca8a8cf0a471bde370f531abd86da7d239802ccdb673e058484f4998d6fc0aec2082932b07431013cb222361e4af3c696f1bb8dc81
data/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/associate_jsonb.svg)](https://badge.fury.io/rb/associate_jsonb)
4
4
 
5
- Basic ActiveRecord Associations using PostgreSQL JSONB columns, with built-in accessors and column indexes
5
+ #### PostgreSQL JSONB extensions including:
6
+ - Basic ActiveRecord Associations using PostgreSQL JSONB columns, with built-in accessors and column indexes
7
+ - Thread-Safe JSONB updates (well, as safe as they can be) using `jsonb_set`
6
8
 
7
9
  <!-- This gem was created as a solution to this [task](http://cultofmartians.com/tasks/active-record-jsonb-associations.html) from [EvilMartians](http://evilmartians.com).
8
10
 
@@ -12,10 +12,16 @@ require "mutex_m"
12
12
 
13
13
  require "zeitwerk"
14
14
  loader = Zeitwerk::Loader.for_gem
15
- loader.inflector.inflect "supported_rails_version" => "SUPPORTED_RAILS_VERSION"
15
+ loader.inflector.inflect(
16
+ "postgresql" => "PostgreSQL",
17
+ "supported_rails_version" => "SUPPORTED_RAILS_VERSION"
18
+ )
16
19
  loader.setup # ready!
17
20
 
18
21
  module AssociateJsonb
22
+ mattr_accessor :safe_hash_classes, default: [
23
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb
24
+ ]
19
25
  end
20
26
 
21
27
 
@@ -25,6 +31,8 @@ ActiveSupport.on_load :active_record do
25
31
 
26
32
  ActiveRecord::Base.include AssociateJsonb::WithStoreAttribute
27
33
  ActiveRecord::Base.include AssociateJsonb::Associations
34
+ ActiveRecord::Base.include AssociateJsonb::AttributeMethods
35
+ ActiveRecord::Base.include AssociateJsonb::Persistence
28
36
 
29
37
  Arel::Nodes.include AssociateJsonb::ArelNodes
30
38
 
@@ -40,6 +48,10 @@ ActiveSupport.on_load :active_record do
40
48
  AssociateJsonb::ArelExtensions::Table
41
49
  )
42
50
 
51
+ Arel::Visitors::PostgreSQL.prepend(
52
+ AssociateJsonb::ArelExtensions::Visitors::PostgreSQL
53
+ )
54
+
43
55
  Arel::Visitors::Visitor.singleton_class.prepend(
44
56
  AssociateJsonb::ArelExtensions::Visitors::Visitor
45
57
  )
@@ -86,6 +98,7 @@ ActiveSupport.on_load :active_record do
86
98
  # )
87
99
 
88
100
  ActiveRecord::Reflection::AbstractReflection.prepend AssociateJsonb::Reflection
101
+ ActiveRecord::PredicateBuilder.prepend AssociateJsonb::PredicateBuilder
89
102
  ActiveRecord::Relation::WhereClause.prepend AssociateJsonb::Relation::WhereClause
90
103
 
91
104
  ActiveRecord::ConnectionAdapters::ReferenceDefinition.prepend(
@@ -23,7 +23,7 @@ module AssociateJsonb
23
23
  def [](name)
24
24
  return super unless store_col = store_tracker&.get(name)
25
25
 
26
- attr = ::Arel::Nodes::Jsonb::DashArrow.
26
+ attr = ::Arel::Nodes::Jsonb::DashDoubleArrow.
27
27
  new(self, self[store_col[:store]], store_col[:key])
28
28
 
29
29
  if cast_as = (store_col[:cast] && store_col[:cast][:sql_type])
@@ -0,0 +1,73 @@
1
+ module AssociateJsonb
2
+ module ArelExtensions
3
+ module Visitors
4
+ module PostgreSQL
5
+ private
6
+ def collect_hash_changes(original, updated, nesting = nil)
7
+ keys = original.keys.map(&:to_s)
8
+ updated_keys = updated.keys.map(&:to_s)
9
+ keys |= updated_keys
10
+ original = original.with_indifferent_access
11
+ updated = updated.with_indifferent_access
12
+ added = []
13
+ deleted = []
14
+ finished = {}
15
+ keys.each do |k|
16
+ if original[k].is_a?(Hash) && updated[k].is_a?(Hash)
17
+ finished[k], a, d = collect_hash_changes(original[k], updated[k], nesting ? "#{nesting},#{k}" : k)
18
+ added |= a
19
+ deleted |= d
20
+ else
21
+ if updated[k].nil?
22
+ deleted << (nesting ? "{#{nesting},#{k}}" : "{#{k}}") if updated_keys.include?(k)
23
+ elsif original[k] != updated[k]
24
+ finished[k] = updated[k]
25
+ added << [(nesting ? "{#{nesting},#{k}}" : "{#{k}}"), updated[k]]
26
+ end
27
+ end
28
+ end
29
+ [ finished, added, deleted ]
30
+ end
31
+
32
+ def is_hash_update?(o, collector)
33
+ collector &&
34
+ Array(collector.value).any? {|v| v.is_a?(String) && (v =~ /UPDATE/) } &&
35
+ AssociateJsonb.safe_hash_classes.any? {|t| o.value.type.is_a?(t) }
36
+ rescue
37
+ false
38
+ end
39
+
40
+ def visit_Arel_Nodes_BindParam(o, collector)
41
+ if is_hash_update?(o, collector)
42
+ value = o.value
43
+
44
+ changes, additions, deletions =
45
+ collect_hash_changes(
46
+ value.original_value.presence || {},
47
+ value.value.presence || {}
48
+ )
49
+
50
+ json = +"COALESCE(#{quote_column_name(o.value.name)}, '{}'::jsonb)"
51
+
52
+ deletions.each do |del|
53
+ json = +"(#{json} #- '#{del}')"
54
+ end
55
+
56
+ additions.each do |add, value|
57
+ collector.add_bind(o.value.with_value_from_user(value)) do |i|
58
+ json = +"jsonb_set(#{json},'#{add}', $#{i}, true)"
59
+ ''
60
+ end
61
+ end
62
+
63
+ collector << json
64
+
65
+ collector
66
+ else
67
+ collector.add_bind(o.value) { |i| "$#{i}" }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -44,23 +44,21 @@ module AssociateJsonb
44
44
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
45
45
 
46
46
  def apply_jsonb_equality(scope, table, jsonb_column, store_key, foreign_key, value, foreign_klass)
47
- sql_type = type = node_klass = nil
47
+ sql_type = type = nil
48
48
  begin
49
49
  type = foreign_klass.attribute_types[foreign_key.to_s]
50
50
  raise "type not found" unless type.present?
51
51
  sql_type = foreign_klass.columns_hash[foreign_key.to_s]
52
52
  raise "not a column" unless sql_type.present?
53
53
  sql_type = sql_type.sql_type
54
- node_klass = Arel::Nodes::Jsonb::DashArrow
55
54
  rescue
56
55
  type = ActiveModel::Type::String.new
57
56
  sql_type = "text"
58
- node_klass = Arel::Nodes::Jsonb::DashDoubleArrow
59
57
  end
60
58
 
61
59
  scope.where!(
62
60
  Arel::Nodes::SqlCastedEquality.new(
63
- node_klass.new(table, table[jsonb_column], store_key),
61
+ Arel::Nodes::Jsonb::DashDoubleArrow.new(table, table[jsonb_column], store_key),
64
62
  sql_type,
65
63
  Arel::Nodes::BindParam.new(
66
64
  ActiveRecord::Relation::QueryAttribute.new(
@@ -4,14 +4,14 @@
4
4
  module AssociateJsonb
5
5
  module Associations
6
6
  module BelongsToAssociation #:nodoc:
7
- def replace_keys(record)
8
- return super unless reflection.options.key?(:store)
9
-
10
- owner[reflection.foreign_key] =
11
- record._read_attribute(
12
- reflection.association_primary_key(record.class)
13
- )
14
- end
7
+ # def replace_keys(record)
8
+ # return super unless reflection.options.key?(:store)
9
+ #
10
+ # owner[reflection.foreign_key] =
11
+ # record._read_attribute(
12
+ # reflection.association_primary_key(record.class)
13
+ # )
14
+ # end
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module AttributeMethods
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Read
10
+ end
11
+
12
+ private
13
+ def attributes_with_info(attribute_names)
14
+ attribute_names.each_with_object({}) do |name, attrs|
15
+ attrs[name] = _fetch_attribute(name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module AttributeMethods
6
+ module Read
7
+ extend ActiveSupport::Concern
8
+
9
+ def _fetch_attribute(attr_name, &block) # :nodoc
10
+ sync_with_transaction_state if @transaction_state&.finalized?
11
+ @attributes.fetch(attr_name.to_s, &block)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,18 +3,25 @@
3
3
  module AssociateJsonb
4
4
  module ConnectionAdapters
5
5
  module ReferenceDefinition #:nodoc:
6
+ ForeignKeyDefinition = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
6
7
  # rubocop:disable Metrics/ParameterLists
7
8
  def initialize(
8
9
  name,
9
10
  store: false,
11
+ store_key: false,
10
12
  **options
11
13
  )
12
14
  @store = store && store.to_sym
15
+ @store_key = store_key && store_key.to_s unless options[:polymorphic]
13
16
 
14
17
  super(name, **options)
15
18
  end
16
19
  # rubocop:enable Metrics/ParameterLists
17
20
 
21
+ def column_name
22
+ store_key || super
23
+ end
24
+
18
25
  def add_to(table)
19
26
  return super unless store
20
27
 
@@ -25,40 +32,51 @@ module AssociateJsonb
25
32
  should_add_col = table.columns.none? {|col| col.name.to_sym == store}
26
33
  end
27
34
 
28
- table.column(store, :jsonb, null: false, default: {}) if should_add_col
35
+ if should_add_col
36
+ opts = { null: false, default: {} }
37
+ table.column(store, :jsonb, **opts)
38
+ end
39
+
40
+ if foreign_key && column_names.length == 1
41
+ fk = ForeignKeyDefinition.new(table.name, foreign_table_name, foreign_key_options)
42
+ columns.each do |col_name, type, _|
43
+ value = <<-SQL.squish
44
+ jsonb_foreign_key(
45
+ '#{fk.to_table}',
46
+ '#{fk.active_record_primary_key}',
47
+ #{store},
48
+ '#{col_name}',
49
+ '#{type}'
50
+ )
51
+ SQL
52
+ table.constraint({
53
+ name: "#{table.name}_#{col_name}_foreign_key",
54
+ value: value
55
+ })
56
+ end
57
+ end
29
58
 
30
59
  return unless index
31
60
 
32
- # should_add_idx = false
33
- # if table.respond_to? :index_exists?
34
- # should_add_idx = !table.index_exists?([ store ], using: :gin)
35
- # elsif table.respond_to? :indexes
36
- # should_add_idx = table.indexes.none? do |idx, opts|
37
- # (idx == [ store ]) \
38
- # && (opts == { using: :gin })
39
- # end
40
- # end
41
- #
42
- # table.index([ store ], using: :gin) if should_add_idx
43
-
44
- column_names.each do |column_name|
61
+ columns.each do |col_name, type, opts|
62
+ type = :text if type == :string
45
63
  table.index(
46
- "CAST (\"#{store}\"->'#{column_name}' AS #{@type || :bigint})",
64
+ "CAST (\"#{store}\"->>'#{col_name}' AS #{type || :bigint})",
47
65
  using: :btree,
48
- name: "index_#{table.name}_on_#{store}_#{column_name}"
66
+ name: "index_#{table.name}_on_#{store}_#{col_name}"
49
67
  )
50
68
 
51
69
  table.index(
52
- "(#{store}->>'#{column_name}')",
70
+ "(\"#{store}\"->>'#{col_name}')",
53
71
  using: :btree,
54
- name: "index_#{table.name}_on_#{store}_#{column_name}_text"
72
+ name: "index_#{table.name}_on_#{store}_#{col_name}_text"
55
73
  )
56
74
  end
57
75
  end
58
76
 
59
77
  protected
60
78
 
61
- attr_reader :store
79
+ attr_reader :store, :store_key
62
80
  end
63
81
  end
64
82
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociateJsonb
4
+ module ConnectionAdapters
5
+ module SchemaCreation
6
+ private
7
+ def visit_AlterTable(o)
8
+ sql = super
9
+ sql << o.constraint_adds.map {|ct| visit_AddConstraint ct }.join(" ")
10
+ sql << o.constraint_drops.map {|ct| visit_DropConstraint ct }.join(" ")
11
+ add_jsonb_function(o, sql)
12
+ end
13
+
14
+ def visit_TableDefinition(...)
15
+ add_jsonb_function(o, super(...))
16
+ end
17
+
18
+ def visit_ColumnDefinition(o)
19
+ column_sql = super
20
+ add_column_constraint!(o, column_sql, column_options(o))
21
+ column_sql
22
+ end
23
+
24
+ def visit_ConstraintDefinition(o)
25
+ +<<-SQL.squish
26
+ CONSTRAINT #{o.name} CHECK (#{o.value})
27
+ SQL
28
+ end
29
+
30
+ def visit_AddConstraint(o)
31
+ "ADD #{accept(o)}"
32
+ end
33
+
34
+ def visit_DropConstraint(o)
35
+ "DROP CONSTRAINT #{o.name}"
36
+ end
37
+
38
+ def add_column_constraint!(o, sql, options)
39
+ if options[:constraint]
40
+ name = value = nil
41
+ if options[:constraint].is_a?(Hash)
42
+ name = quote_column_name(options[:constraint][:name]).presence
43
+ value = options[:constraint][:value]
44
+ else
45
+ value = options[:constraint]
46
+ end
47
+ name ||= quote_column_name("#{o.name}_constraint_#{value.hash}")
48
+ sql << " CONSTRAINT #{name} CHECK (#{value})"
49
+ end
50
+
51
+ sql
52
+ end
53
+
54
+ def add_jsonb_function(o, sql)
55
+ if sql =~ /jsonb_foreign_key/
56
+ visit_AddJsonForeignKeyFunction(o) + sql
57
+ else
58
+ sql
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociateJsonb
4
+ module ConnectionAdapters
5
+ module SchemaCreation
6
+ class AlterTable # :nodoc:
7
+ attr_reader :constraint_adds
8
+ attr_reader :constraint_drops
9
+
10
+ def initialize(td)
11
+ super
12
+ @constraint_adds = []
13
+ @constraint_drops = []
14
+ end
15
+
16
+
17
+ def add_foreign_key(to_table, options)
18
+ @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options)
19
+ end
20
+
21
+ def drop_foreign_key(name)
22
+ @foreign_key_drops << name
23
+ end
24
+
25
+ def add_constraint(options)
26
+ @foreign_key_adds << ConstraintDefinition.new(name, options)
27
+ end
28
+
29
+ def drop_constraint(options)
30
+ @constraint_drops << ConstraintDefinition.new(name, options)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/migration/join_table"
4
+ require "active_support/core_ext/string/access"
5
+ require "active_support/deprecation"
6
+ require "digest/sha2"
7
+
8
+ module AssociateJsonb
9
+ module ConnectionAdapters
10
+ module SchemaStatements
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AssociateJsonb
4
+ module ConnectionAdapters
5
+ module TableDefinition #:nodoc:
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module Persistence
6
+ private
7
+ def _update_row(attribute_names, attempted_action = "update")
8
+ self.class._update_record(
9
+ attributes_with_info(attribute_names),
10
+ @primary_key => id_in_database
11
+ )
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module PredicateBuilder # :nodoc:
6
+ def build_bind_attribute(column_name, value)
7
+ if value.respond_to?(:value_before_type_cast)
8
+ attr = ActiveRecord::Relation::QueryAttribute.new(column_name.to_s, value.value_before_type_cast, table.type(column_name), value)
9
+ else
10
+ attr = ActiveRecord::Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
11
+ end
12
+ Arel::Nodes::BindParam.new(attr)
13
+ end
14
+ end
15
+ end
@@ -60,7 +60,7 @@ module AssociateJsonb
60
60
  Arel::Nodes::NamedFunction.new(
61
61
  "CAST",
62
62
  [
63
- Arel::Nodes::Jsonb::DashArrow.
63
+ Arel::Nodes::Jsonb::DashDoubleArrow.
64
64
  new(table, table[foreign_store_attr], foreign_store_key || key).
65
65
  as(foreign_klass.columns_hash[foreign_key.to_s].sql_type)
66
66
  ]
@@ -83,7 +83,7 @@ module AssociateJsonb
83
83
  Arel::Nodes::NamedFunction.new(
84
84
  "CAST",
85
85
  [
86
- Arel::Nodes::Jsonb::DashArrow.
86
+ Arel::Nodes::Jsonb::DashDoubleArrow.
87
87
  new(foreign_table, foreign_table[jsonb_store_attr], jsonb_store_key || foreign_key).
88
88
  as(klass.columns_hash[key.to_s].sql_type)
89
89
  ]
@@ -1,3 +1,6 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
1
4
  module AssociateJsonb
2
5
  module Relation
3
6
  module WhereClause
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module AssociateJsonb
5
- VERSION = "0.0.4"
5
+ VERSION = "0.0.5"
6
6
  end
@@ -140,20 +140,23 @@ module AssociateJsonb
140
140
 
141
141
  mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
142
142
  def #{store}=(given)
143
- super(given || {})
144
- write_attribute(:#{attribute}, #{on_store_change.call %Q(#{store}["#{key}"])})
145
- if #{attribute}.blank?
146
- #{store}.delete("#{key}")
147
- else
148
- #{store}["#{key}"] = #{attribute}
143
+ if !given
144
+ given = {}
145
+ #{store}.keys.each do |k|
146
+ given[k] = nil
147
+ end
148
+ end
149
+ super(#{store}.deep_merge(given.deep_stringify_keys))
150
+ if #{store}.key?("#{key}")
151
+ write_attribute(:#{attribute}, #{on_store_change.call %Q(#{store}["#{key}"])})
152
+ #{store}["#{key}"] = #{attribute}.presence
149
153
  end
150
154
  #{store}
151
155
  end
152
156
 
153
157
  def #{attribute}=(given)
154
158
  #{on_attr_change}
155
- value = #{store}["#{key}"] = #{attribute}
156
- #{store}.delete("#{key}") if value.nil?
159
+ value = #{store}["#{key}"] = #{attribute}.presence
157
160
  _write_attribute(:#{store}, #{store})
158
161
  value
159
162
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: associate_jsonb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sampson Crowley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-16 00:00:00.000000000 Z
11
+ date: 2020-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -125,6 +125,7 @@ files:
125
125
  - lib/associate_jsonb/arel_extensions/nodes/binary.rb
126
126
  - lib/associate_jsonb/arel_extensions/nodes/table_alias.rb
127
127
  - lib/associate_jsonb/arel_extensions/table.rb
128
+ - lib/associate_jsonb/arel_extensions/visitors/postgresql.rb
128
129
  - lib/associate_jsonb/arel_extensions/visitors/visitor.rb
129
130
  - lib/associate_jsonb/arel_nodes/jsonb/at_arrow.rb
130
131
  - lib/associate_jsonb/arel_nodes/jsonb/attribute.rb
@@ -147,8 +148,16 @@ files:
147
148
  - lib/associate_jsonb/associations/has_many_association.rb
148
149
  - lib/associate_jsonb/associations/join_dependency.rb
149
150
  - lib/associate_jsonb/associations/preloader/association.rb
151
+ - lib/associate_jsonb/attribute_methods.rb
152
+ - lib/associate_jsonb/attribute_methods/read.rb
150
153
  - lib/associate_jsonb/connection_adapters.rb
151
154
  - lib/associate_jsonb/connection_adapters/reference_definition.rb
155
+ - lib/associate_jsonb/connection_adapters/schema_creation.rb
156
+ - lib/associate_jsonb/connection_adapters/schema_creation/alter_table.rb
157
+ - lib/associate_jsonb/connection_adapters/schema_statements.rb
158
+ - lib/associate_jsonb/connection_adapters/table_definition.rb
159
+ - lib/associate_jsonb/persistence.rb
160
+ - lib/associate_jsonb/predicate_builder.rb
152
161
  - lib/associate_jsonb/reflection.rb
153
162
  - lib/associate_jsonb/relation/where_clause.rb
154
163
  - lib/associate_jsonb/supported_rails_version.rb