active_record_extended 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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