actionset 0.8.1 → 0.11.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -5
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -1
  5. data/CHANGELOG +67 -0
  6. data/Gemfile.lock +134 -126
  7. data/README.md +26 -0
  8. data/Rakefile +8 -1
  9. data/actionset.gemspec +2 -2
  10. data/lib/action_set.rb +8 -23
  11. data/lib/action_set/attribute_value.rb +7 -1
  12. data/lib/action_set/filter_instructions.rb +72 -0
  13. data/lib/action_set/helpers/helper_methods.rb +2 -1
  14. data/lib/action_set/helpers/pagination/record_description_for_helper.rb +20 -0
  15. data/lib/action_set/helpers/pagination/record_first_for_helper.rb +20 -0
  16. data/lib/action_set/helpers/pagination/record_last_for_helper.rb +26 -0
  17. data/lib/action_set/helpers/pagination/record_range_for_helper.rb +25 -0
  18. data/lib/action_set/helpers/pagination/record_size_for_helper.rb +9 -0
  19. data/lib/action_set/helpers/pagination/total_pages_for_helper.rb +3 -1
  20. data/lib/action_set/sort_instructions.rb +44 -0
  21. data/lib/active_set.rb +25 -2
  22. data/lib/active_set/active_record_set_instruction.rb +33 -32
  23. data/lib/active_set/attribute_instruction.rb +3 -3
  24. data/lib/active_set/enumerable_set_instruction.rb +13 -24
  25. data/lib/active_set/filtering/active_record/operators.rb +280 -0
  26. data/lib/active_set/filtering/active_record/query_column.rb +35 -0
  27. data/lib/active_set/filtering/active_record/query_value.rb +47 -0
  28. data/lib/active_set/filtering/active_record/set_instruction.rb +29 -0
  29. data/lib/active_set/filtering/active_record/strategy.rb +87 -0
  30. data/lib/active_set/filtering/constants.rb +349 -0
  31. data/lib/active_set/filtering/enumerable/operators.rb +308 -0
  32. data/lib/active_set/filtering/enumerable/set_instruction.rb +98 -0
  33. data/lib/active_set/filtering/enumerable/strategy.rb +90 -0
  34. data/lib/active_set/filtering/operation.rb +5 -8
  35. data/lib/active_set/paginating/active_record_strategy.rb +0 -2
  36. data/lib/active_set/sorting/active_record_strategy.rb +27 -3
  37. data/lib/active_set/sorting/enumerable_strategy.rb +12 -2
  38. data/lib/active_set/sorting/operation.rb +1 -2
  39. data/lib/helpers/flatten_keys_of.rb +53 -0
  40. data/lib/helpers/transform_to_sortable_numeric.rb +3 -3
  41. metadata +26 -13
  42. data/lib/active_set/filtering/active_record_strategy.rb +0 -85
  43. data/lib/active_set/filtering/enumerable_strategy.rb +0 -79
  44. data/lib/helpers/throws.rb +0 -19
  45. data/lib/patches/core_ext/hash/flatten_keys.rb +0 -58
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveSet
4
+ module Filtering
5
+ module ActiveRecord
6
+ module QueryValue
7
+ def query_value
8
+ return @query_value if defined? @query_value
9
+
10
+ query_value = @attribute_instruction.value
11
+ query_value = query_attribute_for(query_value)
12
+ query_value = query_value.downcase if case_insensitive_operation?
13
+
14
+ @query_value = query_value
15
+ end
16
+
17
+ private
18
+
19
+ def query_attribute_for(value)
20
+ return value unless operator_hash.key?(:query_attribute_transformer)
21
+
22
+ context = {
23
+ raw: value,
24
+ sql: to_sql_str(value),
25
+ type: arel_type
26
+ }
27
+
28
+ operator_hash[:query_attribute_transformer].call(context)
29
+ end
30
+
31
+ def to_sql_str(value)
32
+ return value.map { |a| to_sql_str(a) } if value.respond_to?(:map)
33
+
34
+ arel_node = Arel::Nodes::Casted.new(value, arel_column)
35
+ sql_value = arel_node.to_sql
36
+ unwrap_sql_str(sql_value)
37
+ end
38
+
39
+ def unwrap_sql_str(sql_str)
40
+ return sql_str unless sql_str[0] == "'" && sql_str[-1] == "'"
41
+
42
+ sql_str[1..-2]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../active_record_set_instruction'
4
+ require_relative './operators'
5
+ require_relative './query_value'
6
+ require_relative './query_column'
7
+
8
+ class ActiveSet
9
+ module Filtering
10
+ module ActiveRecord
11
+ class SetInstruction < ActiveRecordSetInstruction
12
+ include QueryValue
13
+ include QueryColumn
14
+
15
+ def arel_operator
16
+ operator_hash.fetch(:operator, :eq)
17
+ end
18
+
19
+ private
20
+
21
+ def operator_hash
22
+ instruction_operator = @attribute_instruction.operator
23
+
24
+ Operators.get(instruction_operator)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './set_instruction'
4
+ require 'active_support/core_ext/module/delegation'
5
+
6
+ class ActiveSet
7
+ module Filtering
8
+ module ActiveRecord
9
+ class Strategy
10
+ delegate :attribute_model,
11
+ :query_column,
12
+ :arel_operator,
13
+ :query_value,
14
+ :arel_type,
15
+ :initial_relation,
16
+ :attribute,
17
+ to: :@set_instruction
18
+
19
+ def initialize(set, attribute_instruction)
20
+ @set = set
21
+ @attribute_instruction = attribute_instruction
22
+ @set_instruction = SetInstruction.new(attribute_instruction, set)
23
+ end
24
+
25
+ def execute
26
+ return false unless @set.respond_to? :to_sql
27
+
28
+ if execute_filter_operation?
29
+ statement = filter_operation
30
+ elsif execute_intersect_operation?
31
+ begin
32
+ statement = intersect_operation
33
+ rescue ArgumentError # thrown if merging a non-ActiveRecord::Relation
34
+ return false
35
+ end
36
+ else
37
+ return false
38
+ end
39
+
40
+ statement
41
+ end
42
+
43
+ private
44
+
45
+ def execute_filter_operation?
46
+ return false unless attribute_model
47
+ return false unless attribute_model.respond_to?(:attribute_names)
48
+ return false unless attribute_model.attribute_names.include?(attribute)
49
+
50
+ true
51
+ end
52
+
53
+ def execute_intersect_operation?
54
+ return false unless attribute_model
55
+ return false unless attribute_model.respond_to?(attribute)
56
+ return false if attribute_model.method(attribute).arity.zero?
57
+
58
+ true
59
+ end
60
+
61
+ def filter_operation
62
+ initial_relation
63
+ .where(
64
+ query_column.send(
65
+ arel_operator,
66
+ query_value
67
+ )
68
+ )
69
+ end
70
+
71
+ def intersect_operation
72
+ # NOTE: If merging relations that contain duplicate column conditions,
73
+ # the second condition will replace the first.
74
+ # e.g. Thing.where(id: [1,2]).merge(Thing.where(id: [2,3]))
75
+ # => [Thing<2>, Thing<3>] NOT [Thing<2>]
76
+ initial_relation
77
+ .merge(
78
+ attribute_model.public_send(
79
+ attribute,
80
+ query_value
81
+ )
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,349 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveSet
4
+ module Filtering
5
+ # rubocop:disable Metrics/ModuleLength
6
+ module Constants
7
+ BLANK_VALUES = [nil, ''].freeze
8
+
9
+ PREDICATES = {
10
+ EQ: {
11
+ type: :binary,
12
+ compound: false,
13
+ behavior: :inclusive,
14
+ shorthand: :'=='
15
+ },
16
+ NOT_EQ: {
17
+ type: :binary,
18
+ compound: false,
19
+ behavior: :exclusive,
20
+ shorthand: :'!='
21
+ },
22
+ EQ_ANY: {
23
+ type: :binary,
24
+ compound: true,
25
+ behavior: :inclusive,
26
+ shorthand: :'E=='
27
+ },
28
+ EQ_ALL: {
29
+ type: :binary,
30
+ compound: true,
31
+ behavior: :exclusive,
32
+ shorthand: :'A=='
33
+ },
34
+ NOT_EQ_ANY: {
35
+ type: :binary,
36
+ compound: true,
37
+ behavior: :inclusive,
38
+ shorthand: :'E!='
39
+ },
40
+ NOT_EQ_ALL: {
41
+ type: :binary,
42
+ compound: true,
43
+ behavior: :exclusive,
44
+ shorthand: :'A!='
45
+ },
46
+
47
+ IN: {
48
+ type: :binary,
49
+ compound: true,
50
+ behavior: :inclusive,
51
+ shorthand: :'<<'
52
+ },
53
+ NOT_IN: {
54
+ type: :binary,
55
+ compound: true,
56
+ behavior: :exclusive,
57
+ shorthand: :'!<'
58
+ },
59
+ IN_ANY: {
60
+ type: :binary,
61
+ compound: true,
62
+ behavior: :inclusive,
63
+ shorthand: :'E<<'
64
+ },
65
+ IN_ALL: {
66
+ type: :binary,
67
+ compound: true,
68
+ behavior: :exclusive,
69
+ shorthand: :'A<<'
70
+ },
71
+ NOT_IN_ANY: {
72
+ type: :binary,
73
+ compound: true,
74
+ behavior: :inclusive,
75
+ shorthand: :'E!<'
76
+ },
77
+ NOT_IN_ALL: {
78
+ type: :binary,
79
+ compound: true,
80
+ behavior: :exclusive,
81
+ shorthand: :'A!<'
82
+ },
83
+
84
+ MATCHES: {
85
+ type: :binary,
86
+ compound: false,
87
+ behavior: :inclusive,
88
+ shorthand: :'=~'
89
+ },
90
+ DOES_NOT_MATCH: {
91
+ type: :binary,
92
+ compound: false,
93
+ behavior: :exclusive,
94
+ shorthand: :'!~'
95
+ },
96
+ MATCHES_ANY: {
97
+ type: :binary,
98
+ compound: true,
99
+ behavior: :inclusive,
100
+ shorthand: :'E=~'
101
+ },
102
+ MATCHES_ALL: {
103
+ type: :binary,
104
+ compound: true,
105
+ behavior: :exclusive,
106
+ shorthand: :'A=~'
107
+ },
108
+ DOES_NOT_MATCH_ANY: {
109
+ type: :binary,
110
+ compound: true,
111
+ behavior: :inclusive,
112
+ shorthand: :'E!~'
113
+ },
114
+ DOES_NOT_MATCH_ALL: {
115
+ type: :binary,
116
+ compound: true,
117
+ behavior: :exclusive,
118
+ shorthand: :'A!~'
119
+ },
120
+
121
+ LT: {
122
+ type: :binary,
123
+ compound: false,
124
+ behavior: :exclusive,
125
+ shorthand: :'<'
126
+ },
127
+ LTEQ: {
128
+ type: :binary,
129
+ compound: false,
130
+ behavior: :inclusive,
131
+ shorthand: :'<='
132
+ },
133
+ LT_ANY: {
134
+ type: :binary,
135
+ compound: true,
136
+ behavior: :inconclusive,
137
+ shorthand: :'E<'
138
+ },
139
+ LT_ALL: {
140
+ type: :binary,
141
+ compound: true,
142
+ behavior: :exclusive,
143
+ shorthand: :'A<'
144
+ },
145
+ LTEQ_ANY: {
146
+ type: :binary,
147
+ compound: true,
148
+ behavior: :inclusive,
149
+ shorthand: :'E<='
150
+ },
151
+ LTEQ_ALL: {
152
+ type: :binary,
153
+ compound: true,
154
+ behavior: :inconclusive,
155
+ shorthand: :'A<='
156
+ },
157
+
158
+ GT: {
159
+ type: :binary,
160
+ compound: false,
161
+ behavior: :exclusive,
162
+ shorthand: :'>'
163
+ },
164
+ GTEQ: {
165
+ type: :binary,
166
+ compound: false,
167
+ behavior: :inclusive,
168
+ shorthand: :'>='
169
+ },
170
+ GT_ANY: {
171
+ type: :binary,
172
+ compound: true,
173
+ behavior: :inconclusive,
174
+ shorthand: :'E>'
175
+ },
176
+ GT_ALL: {
177
+ type: :binary,
178
+ compound: true,
179
+ behavior: :exclusive,
180
+ shorthand: :'A>'
181
+ },
182
+ GTEQ_ANY: {
183
+ type: :binary,
184
+ compound: true,
185
+ behavior: :inclusive,
186
+ shorthand: :'E>='
187
+ },
188
+ GTEQ_ALL: {
189
+ type: :binary,
190
+ compound: true,
191
+ behavior: :inconclusive,
192
+ shorthand: :'A>='
193
+ },
194
+
195
+ BETWEEN: {
196
+ type: :binary,
197
+ compound: true,
198
+ behavior: :inclusive,
199
+ shorthand: :'..'
200
+ },
201
+ NOT_BETWEEN: {
202
+ type: :binary,
203
+ compound: true,
204
+ behavior: :exclusive,
205
+ shorthand: :'!.'
206
+ },
207
+
208
+ IS_TRUE: {
209
+ type: :unary,
210
+ operator: :eq,
211
+ compound: false,
212
+ behavior: :inconclusive
213
+ },
214
+ IS_FALSE: {
215
+ type: :unary,
216
+ operator: :eq,
217
+ compound: false,
218
+ behavior: :inconclusive
219
+ },
220
+
221
+ IS_NULL: {
222
+ type: :unary,
223
+ operator: :eq,
224
+ compound: false,
225
+ behavior: :exclusive,
226
+ query_attribute_transformer: proc { |_| nil }
227
+ },
228
+ NOT_NULL: {
229
+ type: :unary,
230
+ operator: :not_eq,
231
+ compound: false,
232
+ behavior: :inclusive,
233
+ query_attribute_transformer: proc { |_| nil }
234
+ },
235
+
236
+ IS_PRESENT: {
237
+ type: :unary,
238
+ operator: :not_eq_all,
239
+ compound: false,
240
+ behavior: :inclusive
241
+ },
242
+ IS_BLANK: {
243
+ type: :unary,
244
+ operator: :eq_any,
245
+ compound: false,
246
+ behavior: :exclusive
247
+ },
248
+
249
+ MATCH_START: {
250
+ type: :binary,
251
+ compound: false,
252
+ behavior: :inclusive
253
+ },
254
+ MATCH_START_ANY: {
255
+ type: :binary,
256
+ compound: true,
257
+ behavior: :inclusive
258
+ },
259
+ MATCH_START_ALL: {
260
+ type: :binary,
261
+ compound: true,
262
+ behavior: :exclusive
263
+ },
264
+ MATCH_NOT_START: {
265
+ type: :binary,
266
+ compound: false,
267
+ behavior: :exclusive
268
+ },
269
+ MATCH_NOT_START_ANY: {
270
+ type: :binary,
271
+ compound: true,
272
+ behavior: :inclusive
273
+ },
274
+ MATCH_NOT_START_ALL: {
275
+ type: :binary,
276
+ compound: true,
277
+ behavior: :exclusive
278
+ },
279
+ MATCH_END: {
280
+ type: :binary,
281
+ compound: false,
282
+ behavior: :inclusive
283
+ },
284
+ MATCH_END_ANY: {
285
+ type: :binary,
286
+ compound: true,
287
+ behavior: :inclusive
288
+ },
289
+ MATCH_END_ALL: {
290
+ type: :binary,
291
+ compound: true,
292
+ behavior: :exclusive
293
+ },
294
+ MATCH_NOT_END: {
295
+ type: :binary,
296
+ compound: false,
297
+ behavior: :exclusive
298
+ },
299
+ MATCH_NOT_END_ANY: {
300
+ type: :binary,
301
+ compound: true,
302
+ behavior: :inclusive
303
+ },
304
+ MATCH_NOT_END_ALL: {
305
+ type: :binary,
306
+ compound: true,
307
+ behavior: :exclusive
308
+ },
309
+ MATCH_CONTAIN: {
310
+ type: :binary,
311
+ compound: false,
312
+ behavior: :inclusive,
313
+ shorthand: :'~*'
314
+ },
315
+ MATCH_CONTAIN_ANY: {
316
+ type: :binary,
317
+ compound: true,
318
+ behavior: :inclusive,
319
+ shorthand: :'E~*'
320
+ },
321
+ MATCH_CONTAIN_ALL: {
322
+ type: :binary,
323
+ compound: true,
324
+ behavior: :exclusive,
325
+ shorthand: :'A~*'
326
+ },
327
+ MATCH_NOT_CONTAIN: {
328
+ type: :binary,
329
+ compound: false,
330
+ behavior: :exclusive,
331
+ shorthand: :'!*'
332
+ },
333
+ MATCH_NOT_CONTAIN_ANY: {
334
+ type: :binary,
335
+ compound: true,
336
+ behavior: :inclusive,
337
+ shorthand: :'E!*'
338
+ },
339
+ MATCH_NOT_CONTAIN_ALL: {
340
+ type: :binary,
341
+ compound: true,
342
+ behavior: :exclusive,
343
+ shorthand: :'A!*'
344
+ }
345
+ }.freeze
346
+ end
347
+ # rubocop:enable Metrics/ModuleLength
348
+ end
349
+ end