active_record_extended 0.1.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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +15 -0
  3. data/.gitignore +18 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +85 -0
  6. data/.ruby-gemset +1 -0
  7. data/.ruby-version +1 -0
  8. data/.travis.yml +35 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +17 -0
  11. data/Gemfile.lock +92 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +45 -0
  14. data/Rakefile +102 -0
  15. data/active_record_extended.gemspec +32 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/gemfiles/activerecord-51.gemfile +8 -0
  19. data/gemfiles/activerecord-52+.gemfile +8 -0
  20. data/gemfiles/activerecord-52.gemfile +8 -0
  21. data/lib/active_record_extended/active_record.rb +15 -0
  22. data/lib/active_record_extended/arel/nodes.rb +61 -0
  23. data/lib/active_record_extended/arel/predications.rb +41 -0
  24. data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +73 -0
  25. data/lib/active_record_extended/arel.rb +6 -0
  26. data/lib/active_record_extended/patch/5_1/where_clause.rb +11 -0
  27. data/lib/active_record_extended/patch/5_2/where_clause.rb +11 -0
  28. data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +20 -0
  29. data/lib/active_record_extended/query_methods/either.rb +60 -0
  30. data/lib/active_record_extended/query_methods/where_chain.rb +106 -0
  31. data/lib/active_record_extended/version.rb +5 -0
  32. data/lib/active_record_extended.rb +9 -0
  33. data/spec/active_record_extended_spec.rb +7 -0
  34. data/spec/query_methods/array_query_spec.rb +64 -0
  35. data/spec/query_methods/either_spec.rb +36 -0
  36. data/spec/query_methods/hash_query_spec.rb +45 -0
  37. data/spec/spec_helper.rb +28 -0
  38. data/spec/sql_inspections/arel/array_spec.rb +63 -0
  39. data/spec/sql_inspections/contains_sql_queries_spec.rb +47 -0
  40. data/spec/sql_inspections/either_sql_spec.rb +55 -0
  41. data/spec/support/database_cleaner.rb +15 -0
  42. data/spec/support/models.rb +20 -0
  43. metadata +203 -0
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "arel/nodes/binary"
4
+
5
+ module Arel
6
+ module Nodes
7
+ class Overlap < Arel::Nodes::Binary
8
+ def operator
9
+ :"&&"
10
+ end
11
+ end
12
+
13
+ class Contains < Arel::Nodes::Binary
14
+ def operator
15
+ :>>
16
+ end
17
+ end
18
+
19
+ class ContainsHStore < Arel::Nodes::Binary
20
+ def operator
21
+ :"@>"
22
+ end
23
+ end
24
+
25
+ class ContainsArray < Arel::Nodes::Binary
26
+ def operator
27
+ :"@>"
28
+ end
29
+ end
30
+
31
+ class ContainedInArray < Arel::Nodes::Binary
32
+ def operator
33
+ :"<@"
34
+ end
35
+ end
36
+
37
+ class ContainsEquals < Arel::Nodes::Binary
38
+ def operator
39
+ :">>="
40
+ end
41
+ end
42
+
43
+ class ContainedWithin < Arel::Nodes::Binary
44
+ def operator
45
+ :<<
46
+ end
47
+ end
48
+
49
+ class ContainedWithinEquals < Arel::Nodes::Binary
50
+ def operator
51
+ :"<<="
52
+ end
53
+ end
54
+
55
+ class Node
56
+ def group_or(right)
57
+ Arel::Nodes::Or.new self, Arel::Nodes::Grouping.new(right)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "arel/predications"
4
+
5
+ module Arel
6
+ module Predications
7
+ def overlap(other)
8
+ Nodes::Overlap.new(self, Nodes.build_quoted(other, self))
9
+ end
10
+
11
+ def contains(other)
12
+ Nodes::Contains.new self, Nodes.build_quoted(other, self)
13
+ end
14
+
15
+ def contained_within(other)
16
+ Nodes::ContainedWithin.new self, Nodes.build_quoted(other, self)
17
+ end
18
+
19
+ def contained_within_or_equals(other)
20
+ Nodes::ContainedWithinEquals.new self, Nodes.build_quoted(other, self)
21
+ end
22
+
23
+ def contained_in_array(other)
24
+ Nodes::ContainedInArray.new self, Nodes.build_quoted(other, self)
25
+ end
26
+
27
+ def contains_or_equals(other)
28
+ Nodes::ContainsEquals.new self, Nodes.build_quoted(other, self)
29
+ end
30
+
31
+ def any(other)
32
+ any_tags_function = Arel::Nodes::NamedFunction.new("ANY", [self])
33
+ Arel::Nodes::Equality.new(Nodes.build_quoted(other, self), any_tags_function)
34
+ end
35
+
36
+ def all(other)
37
+ any_tags_function = Arel::Nodes::NamedFunction.new("ALL", [self])
38
+ Arel::Nodes::Equality.new(Nodes.build_quoted(other, self), any_tags_function)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "arel/visitors/postgresql"
4
+
5
+ module ActiveRecordExtended
6
+ module Visitors
7
+ module PostgreSQLDecorator
8
+ private
9
+
10
+ # rubocop:disable Naming/MethodName
11
+
12
+ def visit_Arel_Nodes_Overlap(object, collector)
13
+ infix_value object, collector, " && "
14
+ end
15
+
16
+ def visit_Arel_Nodes_Contains(object, collector)
17
+ left_column = object.left.relation.name.classify.constantize.columns.detect do |col|
18
+ matchable_column?(col, object)
19
+ end
20
+
21
+ if %i[hstore jsonb].include?(left_column&.type)
22
+ visit_Arel_Nodes_ContainsHStore(object, collector)
23
+ elsif left_column.try(:array)
24
+ visit_Arel_Nodes_ContainsArray(object, collector)
25
+ else
26
+ infix_value object, collector, " >> "
27
+ end
28
+ end
29
+
30
+ def visit_Arel_Nodes_ContainsArray(object, collector)
31
+ infix_value object, collector, " @> "
32
+ end
33
+
34
+ def visit_Arel_Nodes_ContainsHStore(object, collector)
35
+ infix_value object, collector, " @> "
36
+ end
37
+
38
+ def visit_Arel_Nodes_ContainedWithin(object, collector)
39
+ infix_value object, collector, " << "
40
+ end
41
+
42
+ def visit_Arel_Nodes_ContainedWithinEquals(object, collector)
43
+ infix_value object, collector, " <<= "
44
+ end
45
+
46
+ def visit_Arel_Nodes_ContainedInHStore(object, collector)
47
+ infix_value object, collector, " <@ "
48
+ end
49
+
50
+ def visit_Arel_Nodes_ContainedInArray(object, collector)
51
+ infix_value object, collector, " <@ "
52
+ end
53
+
54
+ def visit_Arel_Nodes_ContainsEquals(object, collector)
55
+ infix_value object, collector, " >>= "
56
+ end
57
+
58
+ def visit_Arel_Nodes_AnyOf(object, collector)
59
+ pp object
60
+ pp collector
61
+ collector
62
+ end
63
+
64
+ def matchable_column?(col, object)
65
+ col.name == object.left.name.to_s || col.name == object.left.relation.name.to_s
66
+ end
67
+
68
+ # rubocop:enable Naming/MethodName
69
+ end
70
+ end
71
+ end
72
+
73
+ Arel::Visitors::PostgreSQL.prepend(ActiveRecordExtended::Visitors::PostgreSQLDecorator)
@@ -0,0 +1,6 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ require "active_record_extended/arel/nodes"
5
+ require "active_record_extended/arel/predications"
6
+ require "active_record_extended/arel/visitors/postgresql_decorator"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ module WhereClause
5
+ def modified_predicates(&block)
6
+ ::ActiveRecord::Relation::WhereClause.new(predicates.map(&block), binds)
7
+ end
8
+ end
9
+ end
10
+
11
+ ActiveRecord::Relation::WhereClause.prepend(ActiveRecordExtended::WhereClause)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ module WhereClause
5
+ def modified_predicates(&block)
6
+ ::ActiveRecord::Relation::WhereClause.new(predicates.map(&block))
7
+ end
8
+ end
9
+ end
10
+
11
+ ActiveRecord::Relation::WhereClause.prepend(ActiveRecordExtended::WhereClause)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/predicate_builder"
4
+ require "active_record/relation/predicate_builder/array_handler"
5
+
6
+ module ActiveRecordExtended
7
+ module ArrayHandlerDecorator
8
+ def call(attribute, value)
9
+ cache = ActiveRecord::Base.connection.schema_cache
10
+ if cache.data_source_exists?(attribute.relation.name)
11
+ column = cache.columns(attribute.relation.name).detect { |col| col.name.to_s == attribute.name.to_s }
12
+ return attribute.eq(value) if column.try(:array)
13
+ end
14
+
15
+ super(attribute, value)
16
+ end
17
+ end
18
+ end
19
+
20
+ ActiveRecord::PredicateBuilder::ArrayHandler.prepend(ActiveRecordExtended::ArrayHandlerDecorator)
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ar_outer_joins"
4
+
5
+ module ActiveRecordExtended
6
+ module QueryMethods
7
+ module Either
8
+ XOR_FIELD_SQL = "(CASE WHEN %<t1>s.%<c1>s IS NULL THEN %<t2>s.%<c2>s ELSE %<t1>s.%<c1>s END) "
9
+ XOR_FIELD_KEYS = %i[t1 c1 t2 c2].freeze
10
+
11
+ def either_join(initial_association, fallback_association)
12
+ associations = [initial_association, fallback_association]
13
+ association_options = xor_field_options_for_associations(associations)
14
+ condition__query = xor_field_sql(association_options) + "= #{table_name}.#{primary_key}"
15
+ outer_joins(associations).where(Arel.sql(condition__query))
16
+ end
17
+
18
+ def either_order(direction, **associations_and_columns)
19
+ reflected_columns = map_columns_to_tables(associations_and_columns)
20
+ conditional_query = xor_field_sql(reflected_columns) + sort_order_sql(direction)
21
+ outer_joins(associations_and_columns.keys).order(Arel.sql(conditional_query))
22
+ end
23
+
24
+ private
25
+
26
+ def xor_field_sql(options)
27
+ XOR_FIELD_SQL % Hash[xor_field_options(options)]
28
+ end
29
+
30
+ def sort_order_sql(dir)
31
+ %w[asc desc].include?(dir.to_s) ? dir.to_s : "asc"
32
+ end
33
+
34
+ def xor_field_options(options)
35
+ str_args = options.flatten.take(XOR_FIELD_KEYS.size).map(&:to_s)
36
+ Hash[XOR_FIELD_KEYS.zip(str_args)]
37
+ end
38
+
39
+ def map_columns_to_tables(associations_and_columns)
40
+ if associations_and_columns.respond_to?(:transform_keys)
41
+ associations_and_columns.transform_keys { |assc| reflect_on_association(assc).table_name }
42
+ else
43
+ associations_and_columns.each_with_object({}) do |(assc, value), key_table|
44
+ reflect_table = reflect_on_association(assc).table_name
45
+ key_table[reflect_table] = value
46
+ end
47
+ end
48
+ end
49
+
50
+ def xor_field_options_for_associations(associations)
51
+ associations.each_with_object({}) do |association_name, options|
52
+ reflection = reflect_on_association(association_name)
53
+ options[reflection.table_name] = reflection.foreign_key
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ ActiveRecord::Base.extend(ActiveRecordExtended::QueryMethods::Either)
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ module WhereChain
5
+ def overlap(opts, *rest)
6
+ substitute_comparisons(opts, rest, Arel::Nodes::Overlap, "overlap")
7
+ end
8
+
9
+ def contained_within(opts, *rest)
10
+ substitute_comparisons(opts, rest, Arel::Nodes::ContainedWithin, "contained_within")
11
+ end
12
+
13
+ def contained_within_or_equals(opts, *rest)
14
+ substitute_comparisons(opts, rest, Arel::Nodes::ContainedWithinEquals, "contained_within_or_equals")
15
+ end
16
+
17
+ def contains_or_equals(opts, *rest)
18
+ substitute_comparisons(opts, rest, Arel::Nodes::ContainsEquals, "contains_or_equals")
19
+ end
20
+
21
+ def any(opts, *rest)
22
+ equality_to_function("ANY", opts, rest)
23
+ end
24
+
25
+ def all(opts, *rest)
26
+ equality_to_function("ALL", opts, rest)
27
+ end
28
+
29
+ def contains(opts, *rest)
30
+ build_where_chain(opts, rest) do |arel|
31
+ case arel
32
+ when Arel::Nodes::In, Arel::Nodes::Equality
33
+ column = left_column(arel) || column_from_association(arel)
34
+
35
+ if %i[hstore jsonb].include?(column.type)
36
+ Arel::Nodes::ContainsHStore.new(arel.left, arel.right)
37
+ elsif column.try(:array)
38
+ Arel::Nodes::ContainsArray.new(arel.left, arel.right)
39
+ else
40
+ raise ArgumentError, "Invalid argument for .where.contains(), got #{arel.class}"
41
+ end
42
+ else
43
+ raise ArgumentError, "Invalid argument for .where.contains(), got #{arel.class}"
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def matchable_column?(col, arel)
51
+ col.name == arel.left.name.to_s || col.name == arel.left.relation.name.to_s
52
+ end
53
+
54
+ def column_from_association(arel)
55
+ assoc = assoc_from_related_table(arel)
56
+ assoc.klass.columns.detect { |col| matchable_column?(col, arel) } if assoc
57
+ end
58
+
59
+ def assoc_from_related_table(arel)
60
+ @scope.klass.reflect_on_association(arel.left.relation.name.to_sym) ||
61
+ @scope.klass.reflect_on_association(arel.left.relation.name.singularize.to_sym)
62
+ end
63
+
64
+ def left_column(arel)
65
+ @scope.klass.columns_hash[arel.left.name] || @scope.klass.columns_hash[arel.left.relation.name]
66
+ end
67
+
68
+ def equality_to_function(function_name, opts, rest)
69
+ build_where_chain(opts, rest) do |arel|
70
+ case arel
71
+ when Arel::Nodes::Equality
72
+ Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
73
+ else
74
+ raise ArgumentError, "Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}"
75
+ end
76
+ end
77
+ end
78
+
79
+ def substitute_comparisons(opts, rest, arel_node_class, method)
80
+ build_where_chain(opts, rest) do |arel|
81
+ case arel
82
+ when Arel::Nodes::In, Arel::Nodes::Equality
83
+ arel_node_class.new(arel.left, arel.right)
84
+ else
85
+ raise ArgumentError, "Invalid argument for .where.#{method}(), got #{arel.class}"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ module ActiveRecord
93
+ module QueryMethods
94
+ class WhereChain
95
+ prepend ActiveRecordExtended::WhereChain
96
+
97
+ def build_where_chain(opts, rest, &block)
98
+ where_clause = @scope.send(:where_clause_factory).build(opts, rest)
99
+ @scope.tap do |scope|
100
+ scope.references!(PredicateBuilder.references(opts)) if opts.is_a?(Hash)
101
+ scope.where_clause += where_clause.modified_predicates(&block)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecordExtended
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record_extended/version"
4
+
5
+ require "active_record_extended/active_record"
6
+ require "active_record_extended/arel"
7
+
8
+ module ActiveRecordExtended
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe ActiveRecordExtended do
4
+ it "has a version number" do
5
+ expect(ActiveRecordExtended::VERSION).not_to be nil
6
+ end
7
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record Array Query Methods" do
6
+ let!(:one) { Person.create!(tags: [1, 2, 3], personal_id: 33) }
7
+ let!(:two) { Person.create!(tags: [3, 1, 5], personal_id: 88) }
8
+ let!(:three) { Person.create!(tags: [2, 8, 20], personal_id: 33) }
9
+
10
+ describe "#overlap" do
11
+ it "Should return matched records" do
12
+ query = Person.where.overlap(tags: [1])
13
+ expect(query).to include(one, two)
14
+ expect(query).to_not include(three)
15
+
16
+ query = Person.where.overlap(tags: [2, 3])
17
+ expect(query).to include(one, two, three)
18
+ end
19
+ end
20
+
21
+ describe "#contains" do
22
+ it "returns records that contain elements in an array" do
23
+ query = Person.where.contains(tags: [1, 3])
24
+ expect(query).to include(one, two)
25
+ expect(query).to_not include(three)
26
+
27
+ query = Person.where.overlap(tags: [8, 2])
28
+ expect(query).to include(one, three)
29
+ expect(query).to_not include(two)
30
+ end
31
+ end
32
+
33
+ describe "#any" do
34
+ it "should return any records that match" do
35
+ query = Person.where.any(tags: 3)
36
+ expect(query).to include(one, two)
37
+ expect(query).to_not include(three)
38
+ end
39
+
40
+ it "allows chaining" do
41
+ query = Person.where.any(tags: 3).where(personal_id: 33)
42
+ expect(query).to include(one)
43
+ expect(query).to_not include(two, three)
44
+ end
45
+ end
46
+
47
+ describe "#all" do
48
+ let!(:contains_all) { Person.create!(tags: [1], personal_id: 1) }
49
+ let!(:contains_all_two) { Person.create!(tags: [1], personal_id: 2) }
50
+ let!(:contains_some) { Person.create!(tags: [1, 2], personal_id: 2) }
51
+
52
+ it "should return any records that match" do
53
+ query = Person.where.all(tags: 1)
54
+ expect(query).to include(contains_all, contains_all_two)
55
+ expect(query).to_not include(contains_some)
56
+ end
57
+
58
+ it "allows chaining" do
59
+ query = Person.where.all(tags: 1).where(personal_id: 1)
60
+ expect(query).to include(contains_all)
61
+ expect(query).to_not include(contains_all_two, contains_some)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record Either Methods" do
6
+ let!(:one) { Person.create! }
7
+ let!(:two) { Person.create! }
8
+ let!(:three) { Person.create! }
9
+ let!(:profile_l) { ProfileL.create!(person_id: one.id, likes: 100) }
10
+ let!(:profile_r) { ProfileR.create!(person_id: two.id, dislikes: 50) }
11
+
12
+ describe ".either_join/2" do
13
+ it "Should only only return records that belong to profile L or profile R" do
14
+ query = Person.either_join(:profile_l, :profile_r)
15
+ expect(query).to include(one, two)
16
+ expect(query).to_not include(three)
17
+ end
18
+ end
19
+
20
+ describe ".either_order/2" do
21
+ it "Should not exclude anyone who does not have a relationship" do
22
+ query = Person.either_order(:asc, profile_l: :likes, profile_r: :dislikes)
23
+ expect(query).to include(one, two, three)
24
+ end
25
+
26
+ it "Should order people based on their likes and dislikes in ascended order" do
27
+ query = Person.either_order(:asc, profile_l: :likes, profile_r: :dislikes).where(id: [one.id, two.id])
28
+ expect(query).to match_array([two, one])
29
+ end
30
+
31
+ it "Should order people based on their likes and dislikes in descending order" do
32
+ query = Person.either_order(:desc, profile_l: :likes, profile_r: :dislikes).where(id: [one.id, two.id])
33
+ expect(query).to match_array([one, two])
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Active Record Hash Related Query Methods" do
6
+ let!(:one) { Person.create!(data: { nickname: "george" }, jsonb_data: { payment: "zip" }) }
7
+ let!(:two) { Person.create!(data: { nickname: "dan" }, jsonb_data: { payment: "zipper" }) }
8
+ let!(:three) { Person.create!(data: { nickname: "georgey" }) }
9
+
10
+ describe "#contains" do
11
+ context "HStore Column Type" do
12
+ it "returns records that contain hash elements in joined tables" do
13
+ tag_one = Tag.create!(person_id: one.id)
14
+ tag_two = Tag.create!(person_id: two.id)
15
+
16
+ query = Tag.joins(:person).where.contains(people: { data: { nickname: "george" } })
17
+ expect(query).to include(tag_one)
18
+ expect(query).to_not include(tag_two)
19
+ end
20
+
21
+ it "returns records that contain hash value" do
22
+ query = Person.where.contains(data: { nickname: "george" })
23
+ expect(query).to include(one)
24
+ expect(query).to_not include(two, three)
25
+ end
26
+ end
27
+
28
+ context "JSONB Column Type" do
29
+ it "returns records that contains a json hashed value" do
30
+ query = Person.where.contains(jsonb_data: { payment: "zip" })
31
+ expect(query).to include(one)
32
+ expect(query).to_not include(two, three)
33
+ end
34
+
35
+ it "returns records that contain jsonb elements in joined tables" do
36
+ tag_one = Tag.create!(person_id: one.id)
37
+ tag_two = Tag.create!(person_id: two.id)
38
+
39
+ query = Tag.joins(:person).where.contains(people: { jsonb_data: { payment: "zip" } })
40
+ expect(query).to include(tag_one)
41
+ expect(query).to_not include(tag_two)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+
5
+ unless ENV["DATABASE_URL"]
6
+ require "dotenv"
7
+ Dotenv.load
8
+ end
9
+
10
+ ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"])
11
+
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require File.expand_path(f) }
13
+ Dir["#{File.dirname(__FILE__)}/**/*examples.rb"].each { |f| require f }
14
+
15
+ RSpec.configure do |config|
16
+ # Enable flags like --only-failures and --next-failure
17
+ config.example_status_persistence_file_path = ".rspec_status"
18
+
19
+ # Disable RSpec exposing methods globally on `Module` and `main`
20
+ config.disable_monkey_patching!
21
+
22
+ config.expect_with :rspec do |c|
23
+ c.syntax = :expect
24
+ end
25
+ end
26
+
27
+ # Gem files must be loaded last
28
+ require "active_record_extended"
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe "Array Column Predicates" do
6
+ let(:arel_table) { Person.arel_table }
7
+
8
+ describe "Array Overlap" do
9
+ it "converts Arel overlap statement" do
10
+ query = arel_table.where(arel_table[:tags].overlap(["tag", "tag 2"])).to_sql
11
+ expect(query).to match_regex(/&& '\{"?tag"?,"tag 2"\}'/)
12
+ end
13
+
14
+ it "converts Arel overlap statement" do
15
+ query = arel_table.where(arel_table[:tag_ids].overlap([1, 2])).to_sql
16
+ expect(query).to match_regex(/&& '\{1,2\}'/)
17
+ end
18
+
19
+ it "works with count (and other predicates)" do
20
+ expect(Person.where(arel_table[:tag_ids].overlap([1, 2])).count).to eq 0
21
+ end
22
+ end
23
+
24
+ describe "Array Contains" do
25
+ it "converts Arel contains statement and escapes strings" do
26
+ query = arel_table.where(arel_table[:tags].contains(["tag", "tag 2"])).to_sql
27
+ expect(query).to match_regex(/@> '\{"?tag"?,"tag 2"\}'/)
28
+ end
29
+
30
+ it "converts Arel contains statement with numbers" do
31
+ query = arel_table.where(arel_table[:tag_ids].contains([1, 2])).to_sql
32
+ expect(query).to match_regex(/@> '\{1,2\}'/)
33
+ end
34
+
35
+ it "works with count (and other predicates)" do
36
+ expect(Person.where(arel_table[:tag_ids].contains([1, 2])).count).to eq 0
37
+ end
38
+ end
39
+
40
+ describe "Any Array Element" do
41
+ it "creates any predicates that contain a string value" do
42
+ query = arel_table.where(arel_table[:tags].any("tag")).to_sql
43
+ expect(query).to match_regex(/'tag' = ANY\("people"\."tags"\)/)
44
+ end
45
+
46
+ it "creates any predicates that contain a integer value" do
47
+ query = arel_table.where(arel_table[:tags].any(2)).to_sql
48
+ expect(query).to match_regex(/2 = ANY\("people"\."tags"\)/)
49
+ end
50
+ end
51
+
52
+ describe "All Array Elements" do
53
+ it "create all predicates that contain a string value" do
54
+ query = arel_table.where(arel_table[:tags].all("tag")).to_sql
55
+ expect(query).to match_regex(/'tag' = ALL\("people"\."tags"\)/)
56
+ end
57
+
58
+ it "create all predicates that contain a interger value" do
59
+ query = arel_table.where(arel_table[:tags].all(2)).to_sql
60
+ expect(query).to match_regex(/2 = ALL\("people"\."tags"\)/)
61
+ end
62
+ end
63
+ end