active_hash_relation 1.2.0 → 1.4.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 +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
|
[ ](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
|