active_hash_relation 1.2.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +43 -14
- data/lib/active_hash_relation/column_filters.rb +95 -19
- data/lib/active_hash_relation/filter_applier.rb +15 -2
- data/lib/active_hash_relation/sort_filters.rb +10 -2
- data/lib/active_hash_relation/version.rb +1 -1
- data/spec/tests/not_filter_spec.rb +105 -0
- data/spec/tests/sorting_spec.rb +225 -15
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ba8d3aaed9463815f83717410dabf1d47fd2959
|
4
|
+
data.tar.gz: 14e4eea13e73d7f60ada9f9313a0739e3311fb0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 608d20958f7fd3f37e121abbe57c7c4aa546af10112edcd27e47cbc05f0e702139e6cc23e3bced226c7af8eee354d886b779ffb7c7cd385b639d063c7609ad35
|
7
|
+
data.tar.gz: ebfd652d954d006b0fc13eb4b4b146f95dce213821d52a78f661f7b1a5a80f2a3a9f6723893c5440fae771526bbf45afb36348b47b8d329ef1b0f24db1c781d4
|
data/README.md
CHANGED
@@ -1,36 +1,38 @@
|
|
1
1
|
# ActiveHashRelation
|
2
2
|
[ ![Codeship Status for kollegorna/active_hash_relation](https://app.codeship.com/projects/02f08850-cc66-0134-028f-5ad72e690a75/status?branch=master)](https://app.codeship.com/projects/200194)
|
3
3
|
|
4
|
+
ActiveHashRelation is a complete substitute of ActiveRecord::Relation that allows you to run ActiveRecord queries using only regular Hash using a very powerful yet simple API. It was initially built to allow front-end teams to specify from the API exactly what they need withoug bugging the backend developers, eventually emerged into its own little gem.
|
5
|
+
|
4
6
|
## Introduction
|
5
7
|
Simple gem that allows you to manipulate ActiveRecord::Relation using JSON. For instance:
|
6
8
|
```ruby
|
7
|
-
apply_filters(
|
9
|
+
apply_filters(User.all, {name: 'filippos', created_at: {leq: "2016-10-19"}, role: 'regular', email: {like: 'vasilakis'}})
|
8
10
|
```
|
9
|
-
filter a resource based on it's associations:
|
11
|
+
filter a resource based on it's associations using a NOT filter:
|
10
12
|
```ruby
|
11
|
-
apply_filters(
|
13
|
+
apply_filters(Microposts.all, {updated_at: { geq: "2014-11-2 14:25:04"}, user: {not: {email: vasilakisfil@gmail.com}})
|
12
14
|
```
|
13
|
-
or even filter a resource based on it's associations' associations:
|
15
|
+
or even filter a resource based on it's associations' associations using an OR filter:
|
14
16
|
```ruby
|
15
|
-
apply_filters(
|
17
|
+
apply_filters(Comments.all, {updated_at: { geq: "2014-11-2 14:25:04"}, user: {id: 9, units: {or: [{id: 22}, {id: 21}]} }})
|
16
18
|
```
|
17
|
-
and the list could go on.. Basically
|
19
|
+
and the list could go on.. Basically you can run anything ActiveRecord supports, except from groupping. It's perfect for filtering a collection of resources on APIs.
|
18
20
|
|
19
21
|
It should be noted that `apply_filters` calls `ActiveHashRelation::FilterApplier` class
|
20
22
|
underneath with the same params.
|
21
23
|
|
22
|
-
|
24
|
+
You can also do [__aggregation queries__](#aggregation-queries), like `sum`, `avg`, `min` and `max` on any column.
|
25
|
+
|
26
|
+
_\*A user could retrieve resources based
|
23
27
|
on unknown attributes (attributes not returned from the API) by brute forcing
|
24
|
-
which might or might not be a security issue. If you don't like that check
|
25
|
-
[whitelisting](#whitelisting)._
|
28
|
+
which might or might not be a security issue. If you don't like that check you can specify from the `params` exactly what is allowed and what is not allowed. For more information check: [whitelisting](#whitelisting)._
|
26
29
|
|
27
|
-
*New*! You can now do [__aggregation queries__](#aggregation-queries).
|
28
30
|
|
29
31
|
## Installation
|
30
32
|
|
31
33
|
Add this line to your application's Gemfile:
|
32
34
|
|
33
|
-
gem 'active_hash_relation', '~> 1.
|
35
|
+
gem 'active_hash_relation', '~> 1.3.0
|
34
36
|
|
35
37
|
And then execute:
|
36
38
|
|
@@ -61,7 +63,8 @@ class Api::V1::ResourceController < Api::V1::BaseController
|
|
61
63
|
end
|
62
64
|
```
|
63
65
|
|
64
|
-
If you **need to enable filtering on scopes**, you need to specify that explicitly from the initializer.
|
66
|
+
If you **need to enable filtering on scopes**, you need to specify that explicitly from the initializer. Please run:
|
67
|
+
`bundle exec rails g active_hash_relation:initialize` which will create an initializer with the following content:
|
65
68
|
|
66
69
|
```ruby
|
67
70
|
ActiveHashRelation.configure do |config|
|
@@ -79,6 +82,7 @@ end
|
|
79
82
|
#requires monkeyparched scopes, optional if you don't enable them
|
80
83
|
ActiveHashRelation.initialize!
|
81
84
|
```
|
85
|
+
If you are not using Rails, just add the code above in your equivelant initialize block.
|
82
86
|
|
83
87
|
## The API
|
84
88
|
### Columns
|
@@ -140,8 +144,20 @@ However I would strongly advice you to use a pagination gem like Kaminari, and u
|
|
140
144
|
|
141
145
|
|
142
146
|
### Sorting
|
147
|
+
You can apply sorting using the property as the key of the hash and order as the value. For instance:
|
148
|
+
* `{sort: {created_at: desc}}`
|
149
|
+
|
150
|
+
You can also order by multiple attributes:
|
151
|
+
* `{sort: {created_at: desc, microposts_count: :asc}}`
|
152
|
+
|
153
|
+
If there is no column named after the property value, sorting is skipped.
|
154
|
+
|
155
|
+
#### Deprecated API (will be removed in version 2.0)
|
143
156
|
You can apply sorting using the `property` and `order` attributes. For instance:
|
144
|
-
* `{sort: {property:
|
157
|
+
* `{sort: {property: :created_at, order: :desc}}`
|
158
|
+
|
159
|
+
You can also order by multiple attributes:
|
160
|
+
* `{sort: [{property: :created_at, order: :desc}, {property: :created_at, order: :desc}]}`
|
145
161
|
|
146
162
|
If there is no column named after the property value, sorting is skipped.
|
147
163
|
|
@@ -174,8 +190,21 @@ You can apply an `OR` on associations as well or even nested ones, there isn't m
|
|
174
190
|
I suggest you though to take a look on the [tests](spec/tests/or_filter_spec.rb), cause the syntax gets a bit complex after a while ;)
|
175
191
|
|
176
192
|
|
193
|
+
### NOT Filter
|
194
|
+
You can apply an SQL `NOT` (for ActiveRecord 4+) using the following syntax:
|
195
|
+
`{not: {name: 'Filippos', email: {ends_with: '@gmail.com'}}}`
|
196
|
+
|
197
|
+
It will generate: `WHERE (NOT (users.name = 'Filippos')) AND (NOT (users.email LIKE '%@gmail.com'))`
|
198
|
+
|
199
|
+
You can apply an `NOT` on associations as well or even nested ones, there isn't much limitation on that.
|
200
|
+
I suggest you to also take a look on the [tests](spec/tests/not_filter_spec.rb).
|
201
|
+
|
202
|
+
Also I should note that you need to add specific (partial) queries if you don't want
|
203
|
+
to have performance issues on tables with millions of rows.
|
204
|
+
|
205
|
+
|
177
206
|
### Scopes
|
178
|
-
**Filtering on scopes is not enabled by default
|
207
|
+
**Filtering on scopes is not enabled by default. You need to add the initializer mentioned in the beginning of the [How to use](#how-to-use) section.**.
|
179
208
|
|
180
209
|
Scopes are supported via a tiny monkeypatch in the ActiveRecord's scope class method which holds the name of each scope.
|
181
210
|
The monkey patch is as gentle as it can be: it aliases the method, adds some sugar and executes it.
|
@@ -2,7 +2,11 @@ module ActiveHashRelation::ColumnFilters
|
|
2
2
|
def filter_integer(resource, column, table_name, param)
|
3
3
|
if param.is_a? Array
|
4
4
|
n_param = param.to_s.gsub("\"","'").gsub("[","").gsub("]","") #fix this!
|
5
|
-
|
5
|
+
if @is_not
|
6
|
+
return resource.where.not("#{table_name}.#{column} IN (#{n_param})")
|
7
|
+
else
|
8
|
+
return resource.where("#{table_name}.#{column} IN (#{n_param})")
|
9
|
+
end
|
6
10
|
elsif param.is_a? Hash
|
7
11
|
if !param[:null].nil?
|
8
12
|
return null_filters(resource, table_name, column, param)
|
@@ -10,7 +14,11 @@ module ActiveHashRelation::ColumnFilters
|
|
10
14
|
return apply_leq_geq_le_ge_filters(resource, table_name, column, param)
|
11
15
|
end
|
12
16
|
else
|
13
|
-
|
17
|
+
if @is_not
|
18
|
+
return resource.where.not("#{table_name}.#{column} = ?", param)
|
19
|
+
else
|
20
|
+
return resource.where("#{table_name}.#{column} = ?", param)
|
21
|
+
end
|
14
22
|
end
|
15
23
|
end
|
16
24
|
|
@@ -25,7 +33,11 @@ module ActiveHashRelation::ColumnFilters
|
|
25
33
|
def filter_string(resource, column, table_name, param)
|
26
34
|
if param.is_a? Array
|
27
35
|
n_param = param.to_s.gsub("\"","'").gsub("[","").gsub("]","") #fix this!
|
28
|
-
|
36
|
+
if @is_not
|
37
|
+
return resource.where.not("#{table_name}.#{column} IN (#{n_param})")
|
38
|
+
else
|
39
|
+
return resource.where("#{table_name}.#{column} IN (#{n_param})")
|
40
|
+
end
|
29
41
|
elsif param.is_a? Hash
|
30
42
|
if !param[:null].nil?
|
31
43
|
return null_filters(resource, table_name, column, param)
|
@@ -33,7 +45,11 @@ module ActiveHashRelation::ColumnFilters
|
|
33
45
|
return apply_like_filters(resource, table_name, column, param)
|
34
46
|
end
|
35
47
|
else
|
36
|
-
|
48
|
+
if @is_not
|
49
|
+
return resource.where.not("#{table_name}.#{column} = ?", param)
|
50
|
+
else
|
51
|
+
return resource.where("#{table_name}.#{column} = ?", param)
|
52
|
+
end
|
37
53
|
end
|
38
54
|
end
|
39
55
|
|
@@ -44,7 +60,11 @@ module ActiveHashRelation::ColumnFilters
|
|
44
60
|
def filter_date(resource, column, table_name, param)
|
45
61
|
if param.is_a? Array
|
46
62
|
n_param = param.to_s.gsub("\"","'").gsub("[","").gsub("]","") #fix this!
|
47
|
-
|
63
|
+
if @is_not
|
64
|
+
return resource.where.not("#{table_name}.#{column} IN (#{n_param})")
|
65
|
+
else
|
66
|
+
return resource.where("#{table_name}.#{column} IN (#{n_param})")
|
67
|
+
end
|
48
68
|
elsif param.is_a? Hash
|
49
69
|
if !param[:null].nil?
|
50
70
|
return null_filters(resource, table_name, column, param)
|
@@ -52,7 +72,11 @@ module ActiveHashRelation::ColumnFilters
|
|
52
72
|
return apply_leq_geq_le_ge_filters(resource, table_name, column, param)
|
53
73
|
end
|
54
74
|
else
|
55
|
-
|
75
|
+
if @is_not
|
76
|
+
resource = resource.where.not(column => param)
|
77
|
+
else
|
78
|
+
resource = resource.where(column => param)
|
79
|
+
end
|
56
80
|
end
|
57
81
|
|
58
82
|
return resource
|
@@ -61,7 +85,11 @@ module ActiveHashRelation::ColumnFilters
|
|
61
85
|
def filter_datetime(resource, column, table_name, param)
|
62
86
|
if param.is_a? Array
|
63
87
|
n_param = param.to_s.gsub("\"","'").gsub("[","").gsub("]","") #fix this!
|
64
|
-
|
88
|
+
if @is_not
|
89
|
+
return resource = resource.where.not("#{table_name}.#{column} IN (#{n_param})")
|
90
|
+
else
|
91
|
+
return resource = resource.where("#{table_name}.#{column} IN (#{n_param})")
|
92
|
+
end
|
65
93
|
elsif param.is_a? Hash
|
66
94
|
if !param[:null].nil?
|
67
95
|
return null_filters(resource, table_name, column, param)
|
@@ -69,7 +97,11 @@ module ActiveHashRelation::ColumnFilters
|
|
69
97
|
return apply_leq_geq_le_ge_filters(resource, table_name, column, param)
|
70
98
|
end
|
71
99
|
else
|
72
|
-
|
100
|
+
if @is_not
|
101
|
+
resource = resource.where.not(column => param)
|
102
|
+
else
|
103
|
+
resource = resource.where(column => param)
|
104
|
+
end
|
73
105
|
end
|
74
106
|
|
75
107
|
return resource
|
@@ -85,7 +117,11 @@ module ActiveHashRelation::ColumnFilters
|
|
85
117
|
b_param = ActiveRecord::Type::Boolean.new.type_cast_from_database(param)
|
86
118
|
end
|
87
119
|
|
88
|
-
|
120
|
+
if @is_not
|
121
|
+
resource = resource.where.not(column => b_param)
|
122
|
+
else
|
123
|
+
resource = resource.where(column => b_param)
|
124
|
+
end
|
89
125
|
end
|
90
126
|
end
|
91
127
|
|
@@ -95,15 +131,31 @@ module ActiveHashRelation::ColumnFilters
|
|
95
131
|
return resource.where("#{table_name}.#{column} = ?", param[:eq]) if param[:eq]
|
96
132
|
|
97
133
|
if !param[:leq].blank?
|
98
|
-
|
134
|
+
if @is_not
|
135
|
+
resource = resource.where.not("#{table_name}.#{column} <= ?", param[:leq])
|
136
|
+
else
|
137
|
+
resource = resource.where("#{table_name}.#{column} <= ?", param[:leq])
|
138
|
+
end
|
99
139
|
elsif !param[:le].blank?
|
100
|
-
|
140
|
+
if @is_not
|
141
|
+
resource = resource.where.not("#{table_name}.#{column} < ?", param[:le])
|
142
|
+
else
|
143
|
+
resource = resource.where("#{table_name}.#{column} < ?", param[:le])
|
144
|
+
end
|
101
145
|
end
|
102
146
|
|
103
147
|
if !param[:geq].blank?
|
104
|
-
|
148
|
+
if @is_not
|
149
|
+
resource = resource.where.not("#{table_name}.#{column} >= ?", param[:geq])
|
150
|
+
else
|
151
|
+
resource = resource.where("#{table_name}.#{column} >= ?", param[:geq])
|
152
|
+
end
|
105
153
|
elsif !param[:ge].blank?
|
106
|
-
|
154
|
+
if @is_not
|
155
|
+
resource = resource.where.not("#{table_name}.#{column} > ?", param[:ge])
|
156
|
+
else
|
157
|
+
resource = resource.where("#{table_name}.#{column} > ?", param[:ge])
|
158
|
+
end
|
107
159
|
end
|
108
160
|
|
109
161
|
return resource
|
@@ -114,19 +166,35 @@ module ActiveHashRelation::ColumnFilters
|
|
114
166
|
like_method = "ILIKE" if param[:with_ilike]
|
115
167
|
|
116
168
|
if !param[:starts_with].blank?
|
117
|
-
|
169
|
+
if @is_not
|
170
|
+
resource = resource.where.not("#{table_name}.#{column} #{like_method} ?", "#{param[:starts_with]}%")
|
171
|
+
else
|
172
|
+
resource = resource.where("#{table_name}.#{column} #{like_method} ?", "#{param[:starts_with]}%")
|
173
|
+
end
|
118
174
|
end
|
119
175
|
|
120
176
|
if !param[:ends_with].blank?
|
121
|
-
|
177
|
+
if @is_not
|
178
|
+
resource = resource.where.not("#{table_name}.#{column} #{like_method} ?", "%#{param[:ends_with]}")
|
179
|
+
else
|
180
|
+
resource = resource.where("#{table_name}.#{column} #{like_method} ?", "%#{param[:ends_with]}")
|
181
|
+
end
|
122
182
|
end
|
123
183
|
|
124
184
|
if !param[:like].blank?
|
125
|
-
|
185
|
+
if @is_not
|
186
|
+
resource = resource.where.not("#{table_name}.#{column} #{like_method} ?", "%#{param[:like]}%")
|
187
|
+
else
|
188
|
+
resource = resource.where("#{table_name}.#{column} #{like_method} ?", "%#{param[:like]}%")
|
189
|
+
end
|
126
190
|
end
|
127
191
|
|
128
192
|
if !param[:eq].blank?
|
129
|
-
|
193
|
+
if @is_not
|
194
|
+
resource = resource.where.not("#{table_name}.#{column} = ?", param[:eq])
|
195
|
+
else
|
196
|
+
resource = resource.where("#{table_name}.#{column} = ?", param[:eq])
|
197
|
+
end
|
130
198
|
end
|
131
199
|
|
132
200
|
return resource
|
@@ -134,11 +202,19 @@ module ActiveHashRelation::ColumnFilters
|
|
134
202
|
|
135
203
|
def null_filters(resource, table_name, column, param)
|
136
204
|
if param[:null] == true
|
137
|
-
|
205
|
+
if @is_not
|
206
|
+
resource = resource.where.not("#{table_name}.#{column} IS NULL")
|
207
|
+
else
|
208
|
+
resource = resource.where("#{table_name}.#{column} IS NULL")
|
209
|
+
end
|
138
210
|
end
|
139
211
|
|
140
212
|
if param[:null] == false
|
141
|
-
|
213
|
+
if @is_not
|
214
|
+
resource = resource.where.not("#{table_name}.#{column} IS NOT NULL")
|
215
|
+
else
|
216
|
+
resource = resource.where("#{table_name}.#{column} IS NOT NULL")
|
217
|
+
end
|
142
218
|
end
|
143
219
|
|
144
220
|
return resource
|
@@ -9,7 +9,7 @@ module ActiveHashRelation
|
|
9
9
|
|
10
10
|
attr_reader :configuration
|
11
11
|
|
12
|
-
def initialize(resource, params, include_associations: false, model: nil)
|
12
|
+
def initialize(resource, params, include_associations: false, model: nil, is_not: false)
|
13
13
|
@configuration = Module.nesting.last.configuration
|
14
14
|
@resource = resource
|
15
15
|
if params.respond_to?(:to_unsafe_h)
|
@@ -19,10 +19,12 @@ module ActiveHashRelation
|
|
19
19
|
end
|
20
20
|
@include_associations = include_associations
|
21
21
|
@model = find_model(model)
|
22
|
+
is_not ? @is_not = true : @is_not = false
|
22
23
|
end
|
23
24
|
|
24
25
|
def apply_filters
|
25
26
|
run_or_filters
|
27
|
+
run_not_filters
|
26
28
|
|
27
29
|
table_name = @model.table_name
|
28
30
|
@model.columns.each do |c|
|
@@ -68,7 +70,7 @@ module ActiveHashRelation
|
|
68
70
|
def run_or_filters
|
69
71
|
if @params[:or].is_a?(Array)
|
70
72
|
if ActiveRecord::VERSION::MAJOR < 5
|
71
|
-
return Rails.logger.warn("OR query is supported on ActiveRecord 5+")
|
73
|
+
return Rails.logger.warn("OR query is supported on ActiveRecord 5+")
|
72
74
|
end
|
73
75
|
|
74
76
|
if @params[:or].length >= 2
|
@@ -83,5 +85,16 @@ module ActiveHashRelation
|
|
83
85
|
end
|
84
86
|
end
|
85
87
|
end
|
88
|
+
|
89
|
+
def run_not_filters
|
90
|
+
if @params[:not].is_a?(Hash) && !@params[:not].blank?
|
91
|
+
@resource = self.class.new(
|
92
|
+
@resource,
|
93
|
+
@params[:not],
|
94
|
+
include_associations: @include_associations,
|
95
|
+
is_not: true
|
96
|
+
).apply_filters
|
97
|
+
end
|
98
|
+
end
|
86
99
|
end
|
87
100
|
end
|
@@ -12,8 +12,16 @@ module ActiveHashRelation::SortFilters
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def apply_hash_sort(resource, params, model = nil)
|
15
|
-
if
|
16
|
-
|
15
|
+
if not params[:property].blank?
|
16
|
+
if model.columns.map(&:name).include?(params[:property].to_s)
|
17
|
+
resource = resource.order(params[:property] => (params[:order] || :desc) )
|
18
|
+
end
|
19
|
+
else
|
20
|
+
params.each do |property, order|
|
21
|
+
if model.columns.map(&:name).include?(property.to_s)
|
22
|
+
resource = resource.order(property => (order || :desc) )
|
23
|
+
end
|
24
|
+
end
|
17
25
|
end
|
18
26
|
|
19
27
|
return resource
|
@@ -0,0 +1,105 @@
|
|
1
|
+
describe ActiveHashRelation do
|
2
|
+
include Helpers
|
3
|
+
|
4
|
+
context 'NOT filter' do
|
5
|
+
it "one NOT clause" do
|
6
|
+
hash = {not: {name: 'Filippos'}}
|
7
|
+
|
8
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
9
|
+
expected_query = q(
|
10
|
+
"SELECT users.* FROM users WHERE (NOT (users.name = 'Filippos'))"
|
11
|
+
)
|
12
|
+
|
13
|
+
expect(strip(query)).to eq expected_query.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
it "multiple NOT clauses" do
|
17
|
+
hash = {not: {name: 'Filippos', email: 'vasilakisfil@gmail.com'}}
|
18
|
+
|
19
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
20
|
+
expected_query = q(
|
21
|
+
"SELECT users.* FROM users WHERE",
|
22
|
+
"(NOT (users.name = 'Filippos'))",
|
23
|
+
"AND",
|
24
|
+
"(NOT (users.email = 'vasilakisfil@gmail.com'))"
|
25
|
+
)
|
26
|
+
|
27
|
+
expect(strip(query)).to eq expected_query.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
31
|
+
it "NOT clause inside OR clause" do
|
32
|
+
hash = {or: [{not: {name: 'Filippos', token: '123'}}, {not: {name: 'Vasilis'}}]}
|
33
|
+
|
34
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
35
|
+
expected_query = q(
|
36
|
+
"SELECT users.* FROM users",
|
37
|
+
"WHERE",
|
38
|
+
"(",
|
39
|
+
"(NOT (users.name = 'Filippos')) AND (NOT (users.token = '123'))",
|
40
|
+
"OR",
|
41
|
+
"(NOT (users.name = 'Vasilis'))",
|
42
|
+
")"
|
43
|
+
)
|
44
|
+
|
45
|
+
expect(strip(query)).to eq expected_query.to_s
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "complex NOT clause" do
|
50
|
+
hash = {not: {name: 'Filippos', email: {ends_with: '@gmail.com'}}}
|
51
|
+
|
52
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
53
|
+
expected_query = q(
|
54
|
+
"SELECT users.* FROM users",
|
55
|
+
"WHERE",
|
56
|
+
"(NOT (users.name = 'Filippos'))",
|
57
|
+
"AND",
|
58
|
+
"(NOT (users.email LIKE '%@gmail.com'))",
|
59
|
+
)
|
60
|
+
|
61
|
+
expect(strip(query)).to eq expected_query.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
context "on NULL" do
|
65
|
+
it "NOT clause on null" do
|
66
|
+
hash = {not: {name: {null: true}}}
|
67
|
+
|
68
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
69
|
+
expected_query = q(
|
70
|
+
"SELECT users.* FROM users WHERE (NOT (users.name IS NULL))"
|
71
|
+
)
|
72
|
+
|
73
|
+
expect(strip(query)).to eq expected_query.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
it "NOT clause on not null" do
|
77
|
+
hash = {not: {name: {null: false}}}
|
78
|
+
|
79
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
80
|
+
expected_query = q(
|
81
|
+
"SELECT users.* FROM users WHERE (NOT (users.name IS NOT NULL))"
|
82
|
+
)
|
83
|
+
|
84
|
+
expect(strip(query)).to eq expected_query.to_s
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "NOT clause on associations" do
|
89
|
+
hash = {microposts: {not: {content: 'Sveavägen 4', id: 1}}}
|
90
|
+
|
91
|
+
query = HelperClass.new.apply_filters(User.all, hash, include_associations: true).to_sql
|
92
|
+
expected_query = q(
|
93
|
+
"SELECT users.* FROM users",
|
94
|
+
"INNER JOIN microposts ON microposts.user_id = users.id",
|
95
|
+
"WHERE",
|
96
|
+
"(NOT (microposts.id = 1))",
|
97
|
+
"AND",
|
98
|
+
"(NOT (microposts.content = 'Sveavägen 4'))"
|
99
|
+
)
|
100
|
+
|
101
|
+
expect(strip(query)).to eq expected_query.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
data/spec/tests/sorting_spec.rb
CHANGED
@@ -2,11 +2,12 @@ describe ActiveHashRelation do
|
|
2
2
|
include Helpers
|
3
3
|
|
4
4
|
context 'sorting' do
|
5
|
+
|
5
6
|
context "one where clause" do
|
6
7
|
it "asc" do
|
7
8
|
hash = {
|
8
9
|
microposts_count: 10,
|
9
|
-
sort: {
|
10
|
+
sort: {microposts_count: :asc}
|
10
11
|
}
|
11
12
|
|
12
13
|
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
@@ -19,10 +20,10 @@ describe ActiveHashRelation do
|
|
19
20
|
expect(strip(query)).to eq expected_query.to_s
|
20
21
|
end
|
21
22
|
|
22
|
-
it "
|
23
|
+
it "desc" do
|
23
24
|
hash = {
|
24
25
|
microposts_count: 10,
|
25
|
-
sort: {
|
26
|
+
sort: {microposts_count: :desc}
|
26
27
|
}
|
27
28
|
|
28
29
|
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
@@ -41,7 +42,7 @@ describe ActiveHashRelation do
|
|
41
42
|
hash = {
|
42
43
|
followers_count: {leq: 20},
|
43
44
|
microposts_count: 10,
|
44
|
-
sort: {
|
45
|
+
sort: {microposts_count: :asc}
|
45
46
|
}
|
46
47
|
|
47
48
|
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
@@ -55,11 +56,11 @@ describe ActiveHashRelation do
|
|
55
56
|
expect(strip(query)).to eq expected_query.to_s
|
56
57
|
end
|
57
58
|
|
58
|
-
it "
|
59
|
+
it "desc" do
|
59
60
|
hash = {
|
60
61
|
followers_count: {leq: 20},
|
61
62
|
microposts_count: 10,
|
62
|
-
sort: {
|
63
|
+
sort: {microposts_count: :desc}
|
63
64
|
}
|
64
65
|
|
65
66
|
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
@@ -75,26 +76,235 @@ describe ActiveHashRelation do
|
|
75
76
|
end
|
76
77
|
|
77
78
|
context "multiple sorting properties" do
|
78
|
-
|
79
|
+
context "as a hashe" do
|
80
|
+
it "with single where clause" do
|
81
|
+
hash = {
|
82
|
+
microposts_count: 10,
|
83
|
+
sort: {
|
84
|
+
microposts_count: :asc,
|
85
|
+
followings_count: :desc
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
90
|
+
expected_query = q(
|
91
|
+
"SELECT users.* FROM users",
|
92
|
+
"WHERE (users.microposts_count = 10)",
|
93
|
+
"ORDER BY users.microposts_count ASC, users.followings_count DESC"
|
94
|
+
)
|
95
|
+
|
96
|
+
expect(strip(query)).to eq expected_query.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
it "when the sorting column does not exist" do
|
100
|
+
hash = {
|
101
|
+
microposts_count: 10,
|
102
|
+
sort: {
|
103
|
+
i_do_not_exist: :asc,
|
104
|
+
followings_count: :desc
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
109
|
+
expected_query = q(
|
110
|
+
"SELECT users.* FROM users",
|
111
|
+
"WHERE (users.microposts_count = 10)",
|
112
|
+
"ORDER BY users.followings_count DESC"
|
113
|
+
)
|
114
|
+
|
115
|
+
expect(strip(query)).to eq expected_query.to_s
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "as an array of hashes (not recommended)" do
|
120
|
+
it "with single where clause" do
|
121
|
+
hash = {
|
122
|
+
microposts_count: 10,
|
123
|
+
sort: [{
|
124
|
+
microposts_count: :asc,
|
125
|
+
}, {
|
126
|
+
followings_count: :desc
|
127
|
+
}]
|
128
|
+
}
|
129
|
+
|
130
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
131
|
+
expected_query = q(
|
132
|
+
"SELECT users.* FROM users",
|
133
|
+
"WHERE (users.microposts_count = 10)",
|
134
|
+
"ORDER BY users.microposts_count ASC, users.followings_count DESC"
|
135
|
+
)
|
136
|
+
|
137
|
+
expect(strip(query)).to eq expected_query.to_s
|
138
|
+
end
|
139
|
+
|
140
|
+
it "when the sorting column does not exist" do
|
141
|
+
hash = {
|
142
|
+
microposts_count: 10,
|
143
|
+
sort: [{
|
144
|
+
i_do_not_exist: :asc,
|
145
|
+
}, {
|
146
|
+
followings_count: :desc
|
147
|
+
}]
|
148
|
+
}
|
149
|
+
|
150
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
151
|
+
expected_query = q(
|
152
|
+
"SELECT users.* FROM users",
|
153
|
+
"WHERE (users.microposts_count = 10)",
|
154
|
+
"ORDER BY users.followings_count DESC"
|
155
|
+
)
|
156
|
+
|
157
|
+
expect(strip(query)).to eq expected_query.to_s
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
it "when the sorting column does not exist" do
|
79
162
|
hash = {
|
80
163
|
microposts_count: 10,
|
81
|
-
sort:
|
82
|
-
property: :microposts_count, order: :asc,
|
83
|
-
}, {
|
84
|
-
property: :followings_count, order: :desc
|
85
|
-
}]
|
164
|
+
sort: {i_do_not_exist: :asc}
|
86
165
|
}
|
87
166
|
|
88
167
|
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
89
168
|
expected_query = q(
|
90
169
|
"SELECT users.* FROM users",
|
91
|
-
"WHERE (users.microposts_count = 10)"
|
92
|
-
"ORDER BY users.microposts_count ASC, users.followings_count DESC"
|
170
|
+
"WHERE (users.microposts_count = 10)"
|
93
171
|
)
|
94
172
|
|
95
173
|
expect(strip(query)).to eq expected_query.to_s
|
174
|
+
|
96
175
|
end
|
97
176
|
end
|
98
|
-
end
|
99
177
|
|
178
|
+
context "deprecated API" do
|
179
|
+
context "one where clause" do
|
180
|
+
it "asc" do
|
181
|
+
hash = {
|
182
|
+
microposts_count: 10,
|
183
|
+
sort: {property: :microposts_count, order: :asc}
|
184
|
+
}
|
185
|
+
|
186
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
187
|
+
expected_query = q(
|
188
|
+
"SELECT users.* FROM users",
|
189
|
+
"WHERE (users.microposts_count = 10)",
|
190
|
+
"ORDER BY users.microposts_count ASC"
|
191
|
+
)
|
192
|
+
|
193
|
+
expect(strip(query)).to eq expected_query.to_s
|
194
|
+
end
|
195
|
+
|
196
|
+
it "desc" do
|
197
|
+
hash = {
|
198
|
+
microposts_count: 10,
|
199
|
+
sort: {property: :microposts_count, order: :desc}
|
200
|
+
}
|
201
|
+
|
202
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
203
|
+
expected_query = q(
|
204
|
+
"SELECT users.* FROM users",
|
205
|
+
"WHERE (users.microposts_count = 10)",
|
206
|
+
"ORDER BY users.microposts_count DESC"
|
207
|
+
)
|
208
|
+
|
209
|
+
expect(strip(query)).to eq expected_query.to_s
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context "multiple where clauses" do
|
214
|
+
it "asc" do
|
215
|
+
hash = {
|
216
|
+
followers_count: {leq: 20},
|
217
|
+
microposts_count: 10,
|
218
|
+
sort: {property: :microposts_count, order: :asc}
|
219
|
+
}
|
220
|
+
|
221
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
222
|
+
expected_query = q(
|
223
|
+
"SELECT users.* FROM users",
|
224
|
+
"WHERE (users.microposts_count = 10)",
|
225
|
+
"AND (users.followers_count <= 20)",
|
226
|
+
"ORDER BY users.microposts_count ASC"
|
227
|
+
)
|
228
|
+
|
229
|
+
expect(strip(query)).to eq expected_query.to_s
|
230
|
+
end
|
231
|
+
|
232
|
+
it "desc" do
|
233
|
+
hash = {
|
234
|
+
followers_count: {leq: 20},
|
235
|
+
microposts_count: 10,
|
236
|
+
sort: {property: :microposts_count, order: :desc}
|
237
|
+
}
|
238
|
+
|
239
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
240
|
+
expected_query = q(
|
241
|
+
"SELECT users.* FROM users",
|
242
|
+
"WHERE (users.microposts_count = 10)",
|
243
|
+
"AND (users.followers_count <= 20)",
|
244
|
+
"ORDER BY users.microposts_count DESC"
|
245
|
+
)
|
246
|
+
|
247
|
+
expect(strip(query)).to eq expected_query.to_s
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context "multiple sorting properties" do
|
252
|
+
it "with single where clause" do
|
253
|
+
hash = {
|
254
|
+
microposts_count: 10,
|
255
|
+
sort: [{
|
256
|
+
property: :microposts_count, order: :asc,
|
257
|
+
}, {
|
258
|
+
property: :followings_count, order: :desc
|
259
|
+
}]
|
260
|
+
}
|
261
|
+
|
262
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
263
|
+
expected_query = q(
|
264
|
+
"SELECT users.* FROM users",
|
265
|
+
"WHERE (users.microposts_count = 10)",
|
266
|
+
"ORDER BY users.microposts_count ASC, users.followings_count DESC"
|
267
|
+
)
|
268
|
+
|
269
|
+
expect(strip(query)).to eq expected_query.to_s
|
270
|
+
end
|
271
|
+
|
272
|
+
it "when the sorting column does not exist" do
|
273
|
+
hash = {
|
274
|
+
microposts_count: 10,
|
275
|
+
sort: [{
|
276
|
+
property: :i_do_not_exist, order: :asc,
|
277
|
+
}, {
|
278
|
+
property: :followings_count, order: :desc
|
279
|
+
}]
|
280
|
+
}
|
281
|
+
|
282
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
283
|
+
expected_query = q(
|
284
|
+
"SELECT users.* FROM users",
|
285
|
+
"WHERE (users.microposts_count = 10)",
|
286
|
+
"ORDER BY users.followings_count DESC"
|
287
|
+
)
|
288
|
+
|
289
|
+
expect(strip(query)).to eq expected_query.to_s
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
it "when the sorting column does not exist" do
|
294
|
+
hash = {
|
295
|
+
microposts_count: 10,
|
296
|
+
sort: {property: :i_do_not_exist, order: :asc}
|
297
|
+
}
|
298
|
+
|
299
|
+
query = HelperClass.new.apply_filters(User.all, hash).to_sql
|
300
|
+
expected_query = q(
|
301
|
+
"SELECT users.* FROM users",
|
302
|
+
"WHERE (users.microposts_count = 10)"
|
303
|
+
)
|
304
|
+
|
305
|
+
expect(strip(query)).to eq expected_query.to_s
|
306
|
+
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
100
310
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_hash_relation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Filippos Vasilakis
|
@@ -331,6 +331,7 @@ files:
|
|
331
331
|
- spec/tests/associations/has_one_spec.rb
|
332
332
|
- spec/tests/booleans_spec.rb
|
333
333
|
- spec/tests/limit_spec.rb
|
334
|
+
- spec/tests/not_filter_spec.rb
|
334
335
|
- spec/tests/null_spec.rb
|
335
336
|
- spec/tests/numbers_spec.rb
|
336
337
|
- spec/tests/or_filter_spec.rb
|
@@ -472,6 +473,7 @@ test_files:
|
|
472
473
|
- spec/tests/associations/has_one_spec.rb
|
473
474
|
- spec/tests/booleans_spec.rb
|
474
475
|
- spec/tests/limit_spec.rb
|
476
|
+
- spec/tests/not_filter_spec.rb
|
475
477
|
- spec/tests/null_spec.rb
|
476
478
|
- spec/tests/numbers_spec.rb
|
477
479
|
- spec/tests/or_filter_spec.rb
|