active_record_extended 1.1.0 → 2.0.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 +4 -4
- data/README.md +87 -15
- data/lib/active_record_extended.rb +2 -1
- data/lib/active_record_extended/active_record.rb +2 -9
- data/lib/active_record_extended/active_record/relation_patch.rb +21 -4
- data/lib/active_record_extended/arel.rb +2 -0
- data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
- data/lib/active_record_extended/arel/nodes.rb +32 -41
- data/lib/active_record_extended/arel/predications.rb +4 -1
- data/lib/active_record_extended/arel/sql_literal.rb +16 -0
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +40 -1
- data/lib/active_record_extended/query_methods/any_of.rb +10 -8
- data/lib/active_record_extended/query_methods/either.rb +1 -1
- data/lib/active_record_extended/query_methods/inet.rb +7 -3
- data/lib/active_record_extended/query_methods/json.rb +156 -50
- data/lib/active_record_extended/query_methods/select.rb +118 -0
- data/lib/active_record_extended/query_methods/unionize.rb +14 -43
- data/lib/active_record_extended/query_methods/where_chain.rb +14 -6
- data/lib/active_record_extended/query_methods/window.rb +93 -0
- data/lib/active_record_extended/query_methods/with_cte.rb +102 -35
- data/lib/active_record_extended/utilities/order_by.rb +77 -0
- data/lib/active_record_extended/utilities/support.rb +178 -0
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/any_of_spec.rb +40 -40
- data/spec/query_methods/array_query_spec.rb +14 -14
- data/spec/query_methods/either_spec.rb +14 -14
- data/spec/query_methods/hash_query_spec.rb +11 -11
- data/spec/query_methods/inet_query_spec.rb +33 -31
- data/spec/query_methods/json_spec.rb +42 -27
- data/spec/query_methods/select_spec.rb +115 -0
- data/spec/query_methods/unionize_spec.rb +56 -56
- data/spec/query_methods/window_spec.rb +51 -0
- data/spec/query_methods/with_cte_spec.rb +22 -12
- data/spec/spec_helper.rb +1 -1
- data/spec/sql_inspections/any_of_sql_spec.rb +12 -12
- data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
- data/spec/sql_inspections/arel/array_spec.rb +7 -7
- data/spec/sql_inspections/arel/inet_spec.rb +7 -7
- data/spec/sql_inspections/contains_sql_queries_spec.rb +14 -14
- data/spec/sql_inspections/either_sql_spec.rb +11 -11
- data/spec/sql_inspections/json_sql_spec.rb +44 -8
- data/spec/sql_inspections/unionize_sql_spec.rb +27 -27
- data/spec/sql_inspections/window_sql_spec.rb +98 -0
- data/spec/sql_inspections/with_cte_sql_spec.rb +52 -23
- data/spec/support/models.rb +24 -4
- metadata +31 -20
- data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +0 -87
- data/lib/active_record_extended/utilities.rb +0 -141
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
module ActiveRecordExtended
|
6
|
+
module Utilities
|
7
|
+
module OrderBy
|
8
|
+
def inline_order_by(arel_node, ordering_args)
|
9
|
+
return arel_node unless scope_preprocess_order_args(ordering_args)
|
10
|
+
|
11
|
+
Arel::Nodes::InfixOperation.new("ORDER BY", arel_node, ordering_args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def scope_preprocess_order_args(ordering_args)
|
15
|
+
return false if ordering_args.blank? || !@scope.respond_to?(:preprocess_order_args, true)
|
16
|
+
|
17
|
+
# Sanitation check / resolver (ActiveRecord::Relation#preprocess_order_args)
|
18
|
+
@scope.send(:preprocess_order_args, ordering_args)
|
19
|
+
ordering_args
|
20
|
+
end
|
21
|
+
|
22
|
+
# Processes "ORDER BY" expressions for supported aggregate functions
|
23
|
+
def order_by_expression(order_by)
|
24
|
+
return false unless order_by && order_by.presence.present?
|
25
|
+
|
26
|
+
to_ordered_table_path(order_by)
|
27
|
+
.tap { |order_args| process_ordering_arguments!(order_args) }
|
28
|
+
.tap { |order_args| scope_preprocess_order_args(order_args) }
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Turns a hash into a dot notation path.
|
33
|
+
#
|
34
|
+
# Example:
|
35
|
+
# - Using pre-set directions:
|
36
|
+
# [{ products: { position: :asc, id: :desc } }]
|
37
|
+
# #=> [{ "products.position" => :asc, "products.id" => :desc }]
|
38
|
+
#
|
39
|
+
# - Using fallback directions:
|
40
|
+
# [{products: :position}]
|
41
|
+
# #=> [{"products.position" => :asc}]
|
42
|
+
#
|
43
|
+
def to_ordered_table_path(args)
|
44
|
+
flatten_safely(Array.wrap(args)) do |arg|
|
45
|
+
next arg unless arg.is_a?(Hash)
|
46
|
+
|
47
|
+
arg.each_with_object({}) do |(tbl_or_col, obj), new_hash|
|
48
|
+
if obj.is_a?(Hash)
|
49
|
+
obj.each_pair do |o_key, o_value|
|
50
|
+
new_hash["#{tbl_or_col}.#{o_key}"] = o_value
|
51
|
+
end
|
52
|
+
elsif ::ActiveRecord::QueryMethods::VALID_DIRECTIONS.include?(obj)
|
53
|
+
new_hash[tbl_or_col] = obj
|
54
|
+
elsif obj.nil?
|
55
|
+
new_hash[tbl_or_col.to_s] = :asc
|
56
|
+
else
|
57
|
+
new_hash["#{tbl_or_col}.#{obj}"] = :asc
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_ordering_arguments!(ordering_args)
|
64
|
+
ordering_args.flatten!
|
65
|
+
ordering_args.compact!
|
66
|
+
ordering_args.map! do |arg|
|
67
|
+
next to_arel_sql(arg) unless arg.is_a?(Hash) # ActiveRecord will reflect if an argument is a symbol
|
68
|
+
|
69
|
+
arg.each_with_object({}) do |(field, dir), ordering_obj|
|
70
|
+
# ActiveRecord will not reflect if the Hash keys are a `Arel::Nodes::SqlLiteral` klass
|
71
|
+
ordering_obj[to_arel_sql(field)] = dir.to_s.downcase
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordExtended
|
4
|
+
module Utilities
|
5
|
+
module Support
|
6
|
+
A_TO_Z_KEYS = ("a".."z").to_a.freeze
|
7
|
+
|
8
|
+
# We need to ensure we can flatten nested ActiveRecord::Relations
|
9
|
+
# that might have been nested due to the (splat)*args parameters
|
10
|
+
#
|
11
|
+
# Note: calling `Array.flatten[!]/1` will actually remove all AR relations from the array.
|
12
|
+
#
|
13
|
+
def flatten_to_sql(*values)
|
14
|
+
flatten_safely(values) do |value|
|
15
|
+
value = yield value if block_given?
|
16
|
+
to_arel_sql(value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
alias to_sql_array flatten_to_sql
|
20
|
+
|
21
|
+
def flatten_safely(values, &block)
|
22
|
+
unless values.is_a?(Array)
|
23
|
+
values = yield values if block
|
24
|
+
return [values]
|
25
|
+
end
|
26
|
+
|
27
|
+
values.map { |value| flatten_safely(value, &block) }.reduce(:+)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Applies aliases to the given query
|
31
|
+
# Ex: `SELECT * FROM users` => `(SELECT * FROM users) AS "members"`
|
32
|
+
def nested_alias_escape(query, alias_name)
|
33
|
+
sql_query = generate_grouping(query)
|
34
|
+
Arel::Nodes::As.new(sql_query, to_arel_sql(double_quote(alias_name)))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Wraps subquery into an Aliased ARRAY
|
38
|
+
# Ex: `SELECT * FROM users` => (ARRAY(SELECT * FROM users)) AS "members"
|
39
|
+
def wrap_with_array(arel_or_rel_query, alias_name, order_by: false)
|
40
|
+
if order_by && arel_or_rel_query.is_a?(ActiveRecord::Relation)
|
41
|
+
arel_or_rel_query = arel_or_rel_query.order(order_by)
|
42
|
+
end
|
43
|
+
|
44
|
+
query = Arel::Nodes::Array.new(to_sql_array(arel_or_rel_query))
|
45
|
+
nested_alias_escape(query, alias_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Wraps query into an aggregated array
|
49
|
+
# EX: `(ARRAY_AGG((SELECT * FROM users)) AS "members"`
|
50
|
+
# `(ARRAY_AGG(DISTINCT (SELECT * FROM users)) AS "members"`
|
51
|
+
# `SELECT ARRAY_AGG((id)) AS "ids" FROM users`
|
52
|
+
# `SELECT ARRAY_AGG(DISTINCT (id)) AS "ids" FROM users`
|
53
|
+
def wrap_with_agg_array(arel_or_rel_query, alias_name, order_by: false, distinct: false)
|
54
|
+
distinct = !(!distinct)
|
55
|
+
order_exp = distinct ? nil : order_by # Can't order a distinct agg
|
56
|
+
query = group_when_needed(arel_or_rel_query)
|
57
|
+
query =
|
58
|
+
Arel::Nodes::AggregateFunctionName
|
59
|
+
.new("ARRAY_AGG", to_sql_array(query), distinct)
|
60
|
+
.order_by(order_exp)
|
61
|
+
|
62
|
+
nested_alias_escape(query, alias_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Will attempt to digest and resolve the from clause
|
66
|
+
#
|
67
|
+
# If the from clause is a String, it will check to see if a table reference key has been assigned.
|
68
|
+
# - If one cannot be detected, one will be appended.
|
69
|
+
# - Rails does not allow assigning table references using the `.from/2` method, when its a string / sym type.
|
70
|
+
#
|
71
|
+
# If the from clause is an AR relation; it will duplicate the object.
|
72
|
+
# - Ensures any memorizers are reset (ex: `.to_sql` sets a memorizer on the instance)
|
73
|
+
# - Key's can be assigned using the `.from/2` method.
|
74
|
+
#
|
75
|
+
def from_clause_constructor(from, reference_key)
|
76
|
+
case from
|
77
|
+
when /\s.?#{reference_key}.?$/ # The from clause is a string and has the tbl reference key
|
78
|
+
@scope.unscoped.from(from)
|
79
|
+
when String, Symbol
|
80
|
+
@scope.unscoped.from("#{from} #{reference_key}")
|
81
|
+
else
|
82
|
+
replicate_klass = from.respond_to?(:unscoped) ? from.unscoped : @scope.unscoped
|
83
|
+
replicate_klass.from(from.dup, reference_key)
|
84
|
+
end.unscope(:where)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Will carry defined CTE tables from the nested sub-query and gradually pushes it up to the parents query stack
|
88
|
+
# I.E: It pushes `WITH [:cte_name:] AS(...), ..` to the top of the query structure tree
|
89
|
+
#
|
90
|
+
# SPECIAL GOTCHA NOTE: (if duplicate keys are found) This will favor the parents query `with's` over nested ones!
|
91
|
+
def pipe_cte_with!(subquery)
|
92
|
+
return self unless subquery.try(:with_values?)
|
93
|
+
|
94
|
+
# Add subquery CTE's to the parents query stack. (READ THE SPECIAL NOTE ABOVE!)
|
95
|
+
if @scope.with_values?
|
96
|
+
@scope.cte.pipe_cte_with!(subquery.cte)
|
97
|
+
else
|
98
|
+
# Top level has no with values
|
99
|
+
@scope.with!(subquery.cte)
|
100
|
+
end
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
# Ensures the given value is properly double quoted.
|
106
|
+
# This also ensures we don't have conflicts with reversed keywords.
|
107
|
+
#
|
108
|
+
# IE: `user` is a reserved keyword in PG. But `"user"` is allowed and works the same
|
109
|
+
# when used as an column/tbl alias.
|
110
|
+
def double_quote(value)
|
111
|
+
return if value.nil?
|
112
|
+
|
113
|
+
case value.to_s
|
114
|
+
# Ignore keys that contain double quotes or a Arel.star (*)[all columns]
|
115
|
+
# or if a table has already been explicitly declared (ex: users.id)
|
116
|
+
when "*", /((^".+"$)|(^[[:alpha:]]+\.[[:alnum:]]+))/
|
117
|
+
value
|
118
|
+
else
|
119
|
+
PG::Connection.quote_ident(value.to_s)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Ensures the key is properly single quoted and treated as a actual PG key reference.
|
124
|
+
def literal_key(key)
|
125
|
+
case key
|
126
|
+
when TrueClass then "'t'"
|
127
|
+
when FalseClass then "'f'"
|
128
|
+
when Numeric then key
|
129
|
+
else
|
130
|
+
key = key.to_s
|
131
|
+
key.start_with?("'") && key.end_with?("'") ? key : "'#{key}'"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Converts a potential subquery into a compatible Arel SQL node.
|
136
|
+
#
|
137
|
+
# Note:
|
138
|
+
# We convert relations to SQL to maintain compatibility with Rails 5.1.
|
139
|
+
# Only Rails 5.2+ maintains bound attributes in Arel, so its better to be safe then sorry.
|
140
|
+
# When we drop support for Rails 5.1, we then can then drop the '.to_sql' conversation
|
141
|
+
|
142
|
+
def to_arel_sql(value)
|
143
|
+
case value
|
144
|
+
when Arel::Nodes::Node, Arel::Nodes::SqlLiteral, nil
|
145
|
+
value
|
146
|
+
when ActiveRecord::Relation
|
147
|
+
Arel.sql(value.spawn.to_sql)
|
148
|
+
else
|
149
|
+
Arel.sql(value.respond_to?(:to_sql) ? value.to_sql : value.to_s)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def group_when_needed(arel_or_rel_query)
|
154
|
+
return arel_or_rel_query unless needs_to_be_grouped?(arel_or_rel_query)
|
155
|
+
|
156
|
+
generate_grouping(arel_or_rel_query)
|
157
|
+
end
|
158
|
+
|
159
|
+
def needs_to_be_grouped?(query)
|
160
|
+
query.respond_to?(:to_sql) || (query.is_a?(String) && /^SELECT.+/i.match?(query))
|
161
|
+
end
|
162
|
+
|
163
|
+
def generate_grouping(expr)
|
164
|
+
::Arel::Nodes::Grouping.new(to_arel_sql(expr))
|
165
|
+
end
|
166
|
+
|
167
|
+
def generate_named_function(function_name, *args)
|
168
|
+
args.map! { |arg| to_arel_sql(arg) }
|
169
|
+
function_name = function_name.to_s.upcase
|
170
|
+
::Arel::Nodes::NamedFunction.new(to_arel_sql(function_name), args)
|
171
|
+
end
|
172
|
+
|
173
|
+
def key_generator
|
174
|
+
A_TO_Z_KEYS.sample
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -3,65 +3,65 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
RSpec.describe "Active Record Any / None of Methods" do
|
6
|
-
let!(:one) {
|
7
|
-
let!(:two) {
|
8
|
-
let!(:three) {
|
6
|
+
let!(:one) { User.create!(personal_id: 1) }
|
7
|
+
let!(:two) { User.create!(personal_id: 2) }
|
8
|
+
let!(:three) { User.create!(personal_id: 3) }
|
9
9
|
|
10
|
-
let!(:tag_one) { Tag.create!(
|
11
|
-
let!(:tag_two) { Tag.create!(
|
12
|
-
let!(:tag_three) { Tag.create!(
|
10
|
+
let!(:tag_one) { Tag.create!(user_id: one.id) }
|
11
|
+
let!(:tag_two) { Tag.create!(user_id: two.id) }
|
12
|
+
let!(:tag_three) { Tag.create!(user_id: three.id) }
|
13
13
|
|
14
14
|
describe "where.any_of/1" do
|
15
15
|
it "Should return queries that match any of the outlined queries" do
|
16
|
-
query =
|
16
|
+
query = User.where.any_of({ personal_id: 1 }, { personal_id: 2 })
|
17
17
|
expect(query).to include(one, two)
|
18
18
|
expect(query).to_not include(three)
|
19
19
|
end
|
20
20
|
|
21
21
|
it "Should accept where query predicates" do
|
22
|
-
personal_one =
|
23
|
-
personal_two =
|
24
|
-
query =
|
22
|
+
personal_one = User.where(personal_id: 1)
|
23
|
+
personal_two = User.where(personal_id: 2)
|
24
|
+
query = User.where.any_of(personal_one, personal_two)
|
25
25
|
|
26
26
|
expect(query).to include(one, two)
|
27
27
|
expect(query).to_not include(three)
|
28
28
|
end
|
29
29
|
|
30
30
|
it "Should accept query strings" do
|
31
|
-
personal_one =
|
31
|
+
personal_one = User.where(personal_id: 1)
|
32
32
|
|
33
|
-
query =
|
33
|
+
query = User.where.any_of(personal_one, "personal_id > 2")
|
34
34
|
expect(query).to include(one, three)
|
35
35
|
expect(query).to_not include(two)
|
36
36
|
|
37
|
-
query =
|
37
|
+
query = User.where.any_of(["personal_id >= ?", 2])
|
38
38
|
expect(query).to include(two, three)
|
39
39
|
expect(query).to_not include(one)
|
40
40
|
end
|
41
41
|
|
42
42
|
context "Relationship queries" do
|
43
43
|
it "Finds records that are queried from two or more has_many associations" do
|
44
|
-
|
45
|
-
|
46
|
-
query
|
44
|
+
user_one_tag = Tag.create!(user_id: one.id)
|
45
|
+
user_two_tag = Tag.create!(user_id: two.id)
|
46
|
+
query = Tag.where.any_of(one.hm_tags, two.hm_tags)
|
47
47
|
|
48
|
-
expect(query).to include(tag_one, tag_two,
|
48
|
+
expect(query).to include(tag_one, tag_two, user_one_tag, user_two_tag)
|
49
49
|
expect(query).to_not include(tag_three)
|
50
50
|
end
|
51
51
|
|
52
52
|
it "Finds records that are dynamically joined" do
|
53
|
-
|
54
|
-
|
55
|
-
query
|
53
|
+
user_one_tag = Tag.where(users: { id: one.id }).includes(:user).references(:user)
|
54
|
+
user_two_tag = Tag.where(users: { id: two.id }).joins(:user)
|
55
|
+
query = Tag.where.any_of(user_one_tag, user_two_tag)
|
56
56
|
|
57
57
|
expect(query).to include(tag_one, tag_two)
|
58
58
|
expect(query).to_not include(tag_three)
|
59
59
|
end
|
60
60
|
|
61
61
|
it "Return matched records of a joined table on the parent level" do
|
62
|
-
query = Tag.joins(:
|
63
|
-
{
|
64
|
-
{
|
62
|
+
query = Tag.joins(:user).where.any_of(
|
63
|
+
{ users: { personal_id: 1 } },
|
64
|
+
{ users: { personal_id: 3 } }
|
65
65
|
)
|
66
66
|
|
67
67
|
expect(query).to include(tag_one, tag_three)
|
@@ -72,55 +72,55 @@ RSpec.describe "Active Record Any / None of Methods" do
|
|
72
72
|
|
73
73
|
describe "where.none_of/1" do
|
74
74
|
it "Should return queries that match none of the outlined queries" do
|
75
|
-
query =
|
75
|
+
query = User.where.none_of({ personal_id: 1 }, { personal_id: 2 })
|
76
76
|
expect(query).to include(three)
|
77
77
|
expect(query).to_not include(one, two)
|
78
78
|
end
|
79
79
|
|
80
80
|
it "Should accept where query predicates" do
|
81
|
-
personal_one =
|
82
|
-
personal_two =
|
83
|
-
query =
|
81
|
+
personal_one = User.where(personal_id: 1)
|
82
|
+
personal_two = User.where(personal_id: 2)
|
83
|
+
query = User.where.none_of(personal_one, personal_two)
|
84
84
|
|
85
85
|
expect(query).to include(three)
|
86
86
|
expect(query).to_not include(one, two)
|
87
87
|
end
|
88
88
|
|
89
89
|
it "Should accept query strings" do
|
90
|
-
personal_one =
|
90
|
+
personal_one = User.where(personal_id: 1)
|
91
91
|
|
92
|
-
query =
|
92
|
+
query = User.where.none_of(personal_one, "personal_id > 2")
|
93
93
|
expect(query).to include(two)
|
94
94
|
expect(query).to_not include(one, three)
|
95
95
|
|
96
|
-
query =
|
96
|
+
query = User.where.none_of(["personal_id >= ?", 2])
|
97
97
|
expect(query).to include(one)
|
98
98
|
expect(query).to_not include(two, three)
|
99
99
|
end
|
100
100
|
|
101
101
|
context "Relationship queries" do
|
102
102
|
it "Finds records that are queried from two or more has_many associations" do
|
103
|
-
|
104
|
-
|
105
|
-
query
|
103
|
+
user_one_tag = Tag.create!(user_id: one.id)
|
104
|
+
user_two_tag = Tag.create!(user_id: two.id)
|
105
|
+
query = Tag.where.none_of(one.hm_tags, two.hm_tags)
|
106
106
|
|
107
107
|
expect(query).to include(tag_three)
|
108
|
-
expect(query).to_not include(tag_one, tag_two,
|
108
|
+
expect(query).to_not include(tag_one, tag_two, user_one_tag, user_two_tag)
|
109
109
|
end
|
110
110
|
|
111
111
|
it "Finds records that are dynamically joined" do
|
112
|
-
|
113
|
-
|
114
|
-
query
|
112
|
+
user_one_tag = Tag.where(users: { id: one.id }).includes(:user).references(:user)
|
113
|
+
user_two_tag = Tag.where(users: { id: two.id }).joins(:user)
|
114
|
+
query = Tag.where.none_of(user_one_tag, user_two_tag)
|
115
115
|
|
116
116
|
expect(query).to include(tag_three)
|
117
117
|
expect(query).to_not include(tag_one, tag_two)
|
118
118
|
end
|
119
119
|
|
120
120
|
it "Return matched records of a joined table on the parent level" do
|
121
|
-
query = Tag.joins(:
|
122
|
-
{
|
123
|
-
{
|
121
|
+
query = Tag.joins(:user).where.none_of(
|
122
|
+
{ users: { personal_id: 1 } },
|
123
|
+
{ users: { personal_id: 3 } }
|
124
124
|
)
|
125
125
|
|
126
126
|
expect(query).to include(tag_two)
|
@@ -3,28 +3,28 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
|
5
5
|
RSpec.describe "Active Record Array Query Methods" do
|
6
|
-
let!(:one) {
|
7
|
-
let!(:two) {
|
8
|
-
let!(:three) {
|
6
|
+
let!(:one) { User.create!(tags: [1, 2, 3], personal_id: 33) }
|
7
|
+
let!(:two) { User.create!(tags: [3, 1, 5], personal_id: 88) }
|
8
|
+
let!(:three) { User.create!(tags: [2, 8, 20], personal_id: 33) }
|
9
9
|
|
10
10
|
describe "#overlap" do
|
11
11
|
it "Should return matched records" do
|
12
|
-
query =
|
12
|
+
query = User.where.overlap(tags: [1])
|
13
13
|
expect(query).to include(one, two)
|
14
14
|
expect(query).to_not include(three)
|
15
15
|
|
16
|
-
query =
|
16
|
+
query = User.where.overlap(tags: [2, 3])
|
17
17
|
expect(query).to include(one, two, three)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
describe "#contains" do
|
22
22
|
it "returns records that contain elements in an array" do
|
23
|
-
query =
|
23
|
+
query = User.where.contains(tags: [1, 3])
|
24
24
|
expect(query).to include(one, two)
|
25
25
|
expect(query).to_not include(three)
|
26
26
|
|
27
|
-
query =
|
27
|
+
query = User.where.contains(tags: [8, 2])
|
28
28
|
expect(query).to include(three)
|
29
29
|
expect(query).to_not include(one, two)
|
30
30
|
end
|
@@ -32,31 +32,31 @@ RSpec.describe "Active Record Array Query Methods" do
|
|
32
32
|
|
33
33
|
describe "#any" do
|
34
34
|
it "should return any records that match" do
|
35
|
-
query =
|
35
|
+
query = User.where.any(tags: 3)
|
36
36
|
expect(query).to include(one, two)
|
37
37
|
expect(query).to_not include(three)
|
38
38
|
end
|
39
39
|
|
40
40
|
it "allows chaining" do
|
41
|
-
query =
|
41
|
+
query = User.where.any(tags: 3).where(personal_id: 33)
|
42
42
|
expect(query).to include(one)
|
43
43
|
expect(query).to_not include(two, three)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
describe "#all" do
|
48
|
-
let!(:contains_all) {
|
49
|
-
let!(:contains_all_two) {
|
50
|
-
let!(:contains_some) {
|
48
|
+
let!(:contains_all) { User.create!(tags: [1], personal_id: 1) }
|
49
|
+
let!(:contains_all_two) { User.create!(tags: [1], personal_id: 2) }
|
50
|
+
let!(:contains_some) { User.create!(tags: [1, 2], personal_id: 2) }
|
51
51
|
|
52
52
|
it "should return any records that match" do
|
53
|
-
query =
|
53
|
+
query = User.where.all(tags: 1)
|
54
54
|
expect(query).to include(contains_all, contains_all_two)
|
55
55
|
expect(query).to_not include(contains_some)
|
56
56
|
end
|
57
57
|
|
58
58
|
it "allows chaining" do
|
59
|
-
query =
|
59
|
+
query = User.where.all(tags: 1).where(personal_id: 1)
|
60
60
|
expect(query).to include(contains_all)
|
61
61
|
expect(query).to_not include(contains_all_two, contains_some)
|
62
62
|
end
|