dynamic_scope 0.5.4
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 +7 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +3 -0
- data/Rakefile +4 -0
- data/app/assets/images/dynamic_scope/fieldset-remove.png +0 -0
- data/app/assets/javascripts/angular-dynamic-scope.coffee +201 -0
- data/app/assets/javascripts/angular-multiple-dynamic-scope.coffee +249 -0
- data/app/assets/javascripts/dynamic_scope.coffee +110 -0
- data/app/assets/stylesheets/dynamic_scope.sass +104 -0
- data/config/locales/dynamic_scope.de.yml +24 -0
- data/config/locales/dynamic_scope.en.yml +24 -0
- data/config/locales/dynamic_scope.fr.yml +24 -0
- data/config/locales/dynamic_scope.it.yml +24 -0
- data/dynamic_scope.gemspec +29 -0
- data/lib/dynamic_scope/acts_as_dynamically_scopable.rb +17 -0
- data/lib/dynamic_scope/class_methods.rb +71 -0
- data/lib/dynamic_scope/concerns/active_record.rb +19 -0
- data/lib/dynamic_scope/concerns.rb +2 -0
- data/lib/dynamic_scope/engine.rb +7 -0
- data/lib/dynamic_scope/processor.rb +89 -0
- data/lib/dynamic_scope/query.rb +205 -0
- data/lib/dynamic_scope/query_helpers.rb +11 -0
- data/lib/dynamic_scope/version.rb +5 -0
- data/lib/dynamic_scope/view_helpers.rb +192 -0
- data/lib/dynamic_scope.rb +14 -0
- metadata +135 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
.dynamic_scope
|
|
2
|
+
fieldset
|
|
3
|
+
position: relative
|
|
4
|
+
margin: 5px 0
|
|
5
|
+
a
|
|
6
|
+
position: relative
|
|
7
|
+
top: 5px
|
|
8
|
+
&:before
|
|
9
|
+
content: image-url('dynamic_scope/fieldset-remove.png')
|
|
10
|
+
|
|
11
|
+
.dynamic_scope_add
|
|
12
|
+
text-decoration: none
|
|
13
|
+
font-size: 90%
|
|
14
|
+
span
|
|
15
|
+
text-decoration: underline
|
|
16
|
+
|
|
17
|
+
.dynamic_scope_add_and_submit
|
|
18
|
+
text-align: right
|
|
19
|
+
|
|
20
|
+
input
|
|
21
|
+
margin-bottom: 5px
|
|
22
|
+
|
|
23
|
+
.dynamic_scope_add
|
|
24
|
+
float: left
|
|
25
|
+
|
|
26
|
+
.dynamic_scope_inputs
|
|
27
|
+
float: left
|
|
28
|
+
|
|
29
|
+
.dynamic_scope_clear
|
|
30
|
+
text-decoration: underline
|
|
31
|
+
|
|
32
|
+
.dynamic_scope_value
|
|
33
|
+
width: 24em
|
|
34
|
+
|
|
35
|
+
.dynamic_scope_column, .dynamic_scope_operator
|
|
36
|
+
width: 9em
|
|
37
|
+
|
|
38
|
+
.dynamic-scope
|
|
39
|
+
.inputs
|
|
40
|
+
fieldset
|
|
41
|
+
position: relative
|
|
42
|
+
margin: 5px 0
|
|
43
|
+
|
|
44
|
+
a.fieldset-remove
|
|
45
|
+
display: inline-block
|
|
46
|
+
position: relative
|
|
47
|
+
vertical-align: middle
|
|
48
|
+
top: 4px
|
|
49
|
+
&:before
|
|
50
|
+
position: relative
|
|
51
|
+
top: 4px
|
|
52
|
+
margin-right: 3px
|
|
53
|
+
content: image-url('dynamic_scope/fieldset-remove.png')
|
|
54
|
+
|
|
55
|
+
.key, .operator, .value
|
|
56
|
+
display: inline-block
|
|
57
|
+
position: relative
|
|
58
|
+
box-sizing: border-box
|
|
59
|
+
vertical-align: top
|
|
60
|
+
margin: 0 6px 0 0
|
|
61
|
+
|
|
62
|
+
.key
|
|
63
|
+
width: 25%
|
|
64
|
+
|
|
65
|
+
.operator
|
|
66
|
+
width: 15%
|
|
67
|
+
|
|
68
|
+
.value
|
|
69
|
+
width: auto
|
|
70
|
+
max-width: 60%
|
|
71
|
+
min-width: 40%
|
|
72
|
+
|
|
73
|
+
div.value
|
|
74
|
+
width: 40%
|
|
75
|
+
|
|
76
|
+
> *
|
|
77
|
+
width: 100%
|
|
78
|
+
|
|
79
|
+
&.type-month
|
|
80
|
+
select.month, input.year
|
|
81
|
+
display: inline-block
|
|
82
|
+
width: calc(50% - 3px)
|
|
83
|
+
vertical-align: top
|
|
84
|
+
select.month
|
|
85
|
+
margin-right: 0
|
|
86
|
+
input.year
|
|
87
|
+
margin-right: 6px
|
|
88
|
+
|
|
89
|
+
.fieldset-add
|
|
90
|
+
text-decoration: none
|
|
91
|
+
span
|
|
92
|
+
text-decoration: underline
|
|
93
|
+
|
|
94
|
+
.clearquery
|
|
95
|
+
text-decoration: underline
|
|
96
|
+
|
|
97
|
+
// ui select styling fixes
|
|
98
|
+
.ui-select-match-item
|
|
99
|
+
padding-bottom: 3px
|
|
100
|
+
padding-top: 3px
|
|
101
|
+
margin-bottom: 1px
|
|
102
|
+
|
|
103
|
+
.ui-select-scope
|
|
104
|
+
height: 1.8em !important
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
de:
|
|
2
|
+
dynamic_scope:
|
|
3
|
+
operator:
|
|
4
|
+
matches: enthält
|
|
5
|
+
does_not_match: stimmt nicht überein
|
|
6
|
+
eq: ist
|
|
7
|
+
not_eq: ist nicht
|
|
8
|
+
gteq: mindestens
|
|
9
|
+
lteq: höchstens
|
|
10
|
+
in: umfasst
|
|
11
|
+
not_in: enthält nicht
|
|
12
|
+
begins_with: beginnt mit
|
|
13
|
+
ends_with: endet mit
|
|
14
|
+
eq_since: seit
|
|
15
|
+
gteq_since: mindestens seit
|
|
16
|
+
lteq_since: höchstens seit
|
|
17
|
+
values:
|
|
18
|
+
yes: Ja
|
|
19
|
+
no: Nein
|
|
20
|
+
add_fieldset: Filter hinzufügen
|
|
21
|
+
add_criteria: Filter hinzufügen
|
|
22
|
+
remove_fieldset: entfernen
|
|
23
|
+
clear: Suche zurücksetzen
|
|
24
|
+
submit: Suche
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
en:
|
|
2
|
+
dynamic_scope:
|
|
3
|
+
operator:
|
|
4
|
+
matches: matches
|
|
5
|
+
does_not_match: does not match
|
|
6
|
+
eq: is
|
|
7
|
+
not_eq: is not
|
|
8
|
+
gteq: at least
|
|
9
|
+
lteq: at most
|
|
10
|
+
in: includes
|
|
11
|
+
not_in: does not include
|
|
12
|
+
begins_with: begins with
|
|
13
|
+
ends_with: ends with
|
|
14
|
+
eq_since: since
|
|
15
|
+
gteq_since: at least since
|
|
16
|
+
lteq_since: at most since
|
|
17
|
+
values:
|
|
18
|
+
yes: Yes
|
|
19
|
+
no: No
|
|
20
|
+
add_fieldset: Add search criteria
|
|
21
|
+
add_criteria: Add search criteria
|
|
22
|
+
remove_fieldset: Remove
|
|
23
|
+
clear: Clear search
|
|
24
|
+
submit: Search
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
fr:
|
|
2
|
+
dynamic_scope:
|
|
3
|
+
operator:
|
|
4
|
+
matches: correspond
|
|
5
|
+
does_not_match: ne correspond pas
|
|
6
|
+
eq: est
|
|
7
|
+
not_eq: n'est pas
|
|
8
|
+
gteq: au moins
|
|
9
|
+
lteq: au maximum
|
|
10
|
+
in: comprend
|
|
11
|
+
not_in: ne comprend pas
|
|
12
|
+
begins_with: commence par
|
|
13
|
+
ends_with: se termine par
|
|
14
|
+
eq_since: depuis
|
|
15
|
+
gteq_since: depuis au moins
|
|
16
|
+
lteq_since: au maximum depuis
|
|
17
|
+
values:
|
|
18
|
+
yes: Oui
|
|
19
|
+
no: Non
|
|
20
|
+
add_fieldset: Ajouter filtre
|
|
21
|
+
add_criteria: Ajouter filtre
|
|
22
|
+
remove_fieldset: Supprimer
|
|
23
|
+
clear: Réinitialiser recherche
|
|
24
|
+
submit: Rechercher
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
it:
|
|
2
|
+
dynamic_scope:
|
|
3
|
+
operator:
|
|
4
|
+
matches: fiammiferi
|
|
5
|
+
does_not_match: non corrisponde
|
|
6
|
+
eq: è
|
|
7
|
+
not_eq: non è
|
|
8
|
+
gteq: almeno
|
|
9
|
+
lteq: al massimo
|
|
10
|
+
in: include
|
|
11
|
+
not_in: non include
|
|
12
|
+
begins_with: inizia con
|
|
13
|
+
ends_with: finisce con
|
|
14
|
+
eq_since: da
|
|
15
|
+
gteq_since: almendo since
|
|
16
|
+
lteq_since: al massimo da
|
|
17
|
+
values:
|
|
18
|
+
yes: Sì
|
|
19
|
+
no: No
|
|
20
|
+
add_fieldset: Aggiungi criteri di ricerca
|
|
21
|
+
add_criteria: Aggiungi criteri di ricerca
|
|
22
|
+
remove_fieldset: Rimuovi
|
|
23
|
+
clear: Ricerca chiara
|
|
24
|
+
submit: Ricerca
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "dynamic_scope/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "dynamic_scope"
|
|
7
|
+
s.version = DynamicScope::Version::VERSION
|
|
8
|
+
s.authors = ["Aleksandr Balakiriev"]
|
|
9
|
+
s.email = ["balakirevs@i.ua"]
|
|
10
|
+
s.homepage = ""
|
|
11
|
+
s.summary = %q{Dynamic scope for rails}
|
|
12
|
+
s.description = %q{Helpers and angular module to make fancy dynamic scopes with arel.}
|
|
13
|
+
|
|
14
|
+
s.required_ruby_version = ">= 3.2.0"
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
|
18
|
+
end
|
|
19
|
+
s.bindir = "exe"
|
|
20
|
+
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
21
|
+
s.require_paths = ["lib"]
|
|
22
|
+
|
|
23
|
+
s.add_dependency('sass-rails', '>= 6.0')
|
|
24
|
+
s.add_dependency('coffee-rails', '>= 5.0')
|
|
25
|
+
s.add_dependency('i18n-js', '~> 3.9.2')
|
|
26
|
+
|
|
27
|
+
s.add_development_dependency "rspec", "~> 3.13"
|
|
28
|
+
s.add_development_dependency "rails", ">= 8.0"
|
|
29
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'dynamic_scope/class_methods'
|
|
2
|
+
|
|
3
|
+
module DynamicScope
|
|
4
|
+
module ActsAsDynamicallyScopable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def acts_as_dynamically_scopable(config)
|
|
9
|
+
class_eval do
|
|
10
|
+
include DynamicScope::ActiveRecord::Concern
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
self.dynamic_scope_config = config
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module DynamicScope
|
|
2
|
+
module ClassMethods
|
|
3
|
+
def dynamic_scope_is_empty?(params)
|
|
4
|
+
params.blank? || params.select{|key, value| value["value"].present? }.blank?
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def dynamic_scope_to_ransack(params)
|
|
10
|
+
return {} unless params
|
|
11
|
+
params = params.deep_dup
|
|
12
|
+
g = 1
|
|
13
|
+
q = {
|
|
14
|
+
'g' => {
|
|
15
|
+
'0' => {
|
|
16
|
+
'm' => 'and'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
params.each_pair do |index, args|
|
|
21
|
+
if args['name'] == 'any_field'
|
|
22
|
+
q['g'][(g+=1).to_s] = {
|
|
23
|
+
'm' => 'or',
|
|
24
|
+
'c' => {}
|
|
25
|
+
}
|
|
26
|
+
self.dynamic_scope_columns.each_with_index do |col, i|
|
|
27
|
+
q['g'][g.to_s]['c'][i.to_s] = {}
|
|
28
|
+
c = q['g'][g.to_s]['c'][i.to_s]
|
|
29
|
+
if col.first == :string && col.last != :any_field
|
|
30
|
+
c['a'] = { '0' => { 'name' => col.last.to_s } }
|
|
31
|
+
c['v'] = { '0' => { 'value' => args['value'] } }
|
|
32
|
+
end
|
|
33
|
+
c['p'] = args['operator']
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
col = dyn_scope_column(args['name'])
|
|
37
|
+
if col and col[0] == :age and col[2].present?
|
|
38
|
+
args = age_to_date(args)
|
|
39
|
+
args['name'] = col[2].to_s
|
|
40
|
+
end
|
|
41
|
+
q['g']['0']['c'] ||= {}
|
|
42
|
+
size = q['g']['0']['c'].size
|
|
43
|
+
q['g']['0']['c'][size.to_s] = {}
|
|
44
|
+
c = q['g']['0']['c'][size.to_s]
|
|
45
|
+
|
|
46
|
+
c['a'] = { '0' => { 'name' => args['name'] } }
|
|
47
|
+
c['p'] = args['operator']
|
|
48
|
+
c['v'] = { '0' => { 'value' => args['value'] } }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
q
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def dyn_scope_column(name)
|
|
55
|
+
dynamic_scope_columns.select{|c| c[1] == name.to_sym}.first
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def age_to_date(args)
|
|
59
|
+
args['operator'] =
|
|
60
|
+
case args['operator']
|
|
61
|
+
when 'gt' then 'lt'
|
|
62
|
+
when 'lt' then 'gt'
|
|
63
|
+
else args['operator']
|
|
64
|
+
end
|
|
65
|
+
if args['value'].match(/^[0-9]+$/)
|
|
66
|
+
args['value'] = (Date.today - args['value'].to_i.years).to_s
|
|
67
|
+
end
|
|
68
|
+
args
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module DynamicScope::Concerns::ActiveRecord
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
class_attribute :dynamic_scope_config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def dynamic_scope(query, config = nil)
|
|
12
|
+
config ||= self.dynamic_scope_config
|
|
13
|
+
scope = DynamicScope::Processor.new(all, query, config).scope
|
|
14
|
+
all.joins(scope.joins_values).distinct
|
|
15
|
+
.includes(scope.includes_values)
|
|
16
|
+
.and(scope.distinct)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require 'dynamic_scope/query'
|
|
2
|
+
|
|
3
|
+
# Processor for dynamic scope.
|
|
4
|
+
#
|
|
5
|
+
# usage:
|
|
6
|
+
#
|
|
7
|
+
# params = {
|
|
8
|
+
# '0' => {key: 'id', operator: 'eq', value: '1'},
|
|
9
|
+
# }
|
|
10
|
+
#
|
|
11
|
+
# config = {
|
|
12
|
+
# id: {type: :integer}
|
|
13
|
+
# }
|
|
14
|
+
# processor = DynamicScope::Processor.new(Model.all, params, config)
|
|
15
|
+
# Model.all.merge(processor.scope)
|
|
16
|
+
#
|
|
17
|
+
#
|
|
18
|
+
# Config format:
|
|
19
|
+
# {
|
|
20
|
+
# key: {
|
|
21
|
+
# # Required
|
|
22
|
+
# type: :integer || :string || :enum || :datetime,
|
|
23
|
+
#
|
|
24
|
+
# # Required for type :enum
|
|
25
|
+
# values: ['value_1', 'value_2'],
|
|
26
|
+
#
|
|
27
|
+
# # Optional: uses key OR given value to determine which scope to call
|
|
28
|
+
# # scope receives (operator, value) as arguments.
|
|
29
|
+
# scope: true || :some_scope_name,
|
|
30
|
+
#
|
|
31
|
+
# # Opional: adds "OR :attribute IS NULL" to SQL statement
|
|
32
|
+
# # when operator is negative.
|
|
33
|
+
# null: true,
|
|
34
|
+
#
|
|
35
|
+
# # Optional: uses given value if attribute_name differs from key
|
|
36
|
+
# attribute_name: :some_column,
|
|
37
|
+
#
|
|
38
|
+
# # Optional: use when attribute resides in some other model,
|
|
39
|
+
# # uses rails relations to generate joins.
|
|
40
|
+
# # If given, the scope option affects this class.
|
|
41
|
+
# relation: :parent_model,
|
|
42
|
+
# relation: {parent_model: :granparent_model},
|
|
43
|
+
#
|
|
44
|
+
# # Optional: use to modify value coming from params
|
|
45
|
+
# value: lambda{|value| "#{value}"}
|
|
46
|
+
# }
|
|
47
|
+
#
|
|
48
|
+
# Params format:
|
|
49
|
+
# {
|
|
50
|
+
# '0' => {
|
|
51
|
+
# # Required: hash key of config
|
|
52
|
+
# key: 'key',
|
|
53
|
+
#
|
|
54
|
+
# # Required:
|
|
55
|
+
# # Supported operators are defined in DynamicScope::Query::OPERATORS
|
|
56
|
+
# operator: 'operator',
|
|
57
|
+
#
|
|
58
|
+
# # Required:
|
|
59
|
+
# value: 'value'
|
|
60
|
+
#
|
|
61
|
+
# # Opional: adds "OR :attribute IS NULL" to SQL statement
|
|
62
|
+
# null: true,
|
|
63
|
+
# }
|
|
64
|
+
# }
|
|
65
|
+
class DynamicScope::Processor
|
|
66
|
+
attr_reader :params, :config
|
|
67
|
+
|
|
68
|
+
def initialize(scope, params, config)
|
|
69
|
+
@scope = scope
|
|
70
|
+
@params = params
|
|
71
|
+
@config = config
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def scope
|
|
75
|
+
@params.values.inject(@scope) do |memo, query|
|
|
76
|
+
if (query_scope = DynamicScope::Query.new(@scope, query, @config).scope)
|
|
77
|
+
if query_scope
|
|
78
|
+
memo = memo.joins(query_scope.joins_values) if query_scope.try(:joins_values).present?
|
|
79
|
+
memo = memo.includes(query_scope.includes_values) if query_scope.try(:includes_values).present?
|
|
80
|
+
memo.distinct.and(query_scope.distinct).distinct
|
|
81
|
+
else
|
|
82
|
+
memo.distinct
|
|
83
|
+
end
|
|
84
|
+
else
|
|
85
|
+
memo.distinct
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
require 'dynamic_scope/query_helpers'
|
|
2
|
+
|
|
3
|
+
class DynamicScope::Query
|
|
4
|
+
include DynamicScope::QueryHelpers
|
|
5
|
+
|
|
6
|
+
class UnallowedOperator < ::Exception
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class InvalidValue < ::Exception
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class MissingRequiredKey < ::Exception
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class UnsupportedType < ::Exception
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
REQUIRED_KEYS = [:key, :operator, :value].freeze
|
|
19
|
+
|
|
20
|
+
SUPPORTED_TYPES = [:integer, :string, :enum, :year, :month, :date, :date_future, :datetime, :boolean].freeze
|
|
21
|
+
|
|
22
|
+
OPERATORS = {
|
|
23
|
+
integer: [:eq, :not_eq, :gteq, :lteq, :in, :not_in],
|
|
24
|
+
string: [:matches, :does_not_match, :begins_with, :ends_with, :eq, :not_eq],
|
|
25
|
+
enum: [:eq, :not_eq, :in, :not_in],
|
|
26
|
+
year: [:eq, :gteq, :lteq],
|
|
27
|
+
month: [:eq, :gteq, :lteq, :eq_since, :gteq_since, :lteq_since],
|
|
28
|
+
date: [:eq, :gteq, :lteq],
|
|
29
|
+
date_future: [:eq, :gteq, :lteq],
|
|
30
|
+
datetime: [:eq, :gteq, :lteq],
|
|
31
|
+
boolean: [:eq, :not_eq]
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
NEGATIVE_OPERATORS = [:not_eq, :does_not_match, :not_in].freeze
|
|
35
|
+
|
|
36
|
+
attr_accessor :query, :config
|
|
37
|
+
|
|
38
|
+
def initialize(_scope, _query, _config)
|
|
39
|
+
@query = _query
|
|
40
|
+
@config = _config
|
|
41
|
+
|
|
42
|
+
check_query_for_missing_keys!
|
|
43
|
+
|
|
44
|
+
@scope = query_config[:relation].present? ? _scope.joins(query_config[:relation]) : _scope
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def query_config
|
|
48
|
+
@config[query_key]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def arel_query
|
|
52
|
+
arel = scope_klass.arel_table[attribute_name]
|
|
53
|
+
aq = arel.send(operator_for_arel, value_for_arel)
|
|
54
|
+
if query_param_truthy?(@query[:null] || query_config[:null]) and NEGATIVE_OPERATORS.include?(operator)
|
|
55
|
+
aq = aq.or(arel.eq(nil))
|
|
56
|
+
end
|
|
57
|
+
aq
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def scope
|
|
61
|
+
return nil if value.blank?
|
|
62
|
+
if custom_scope?
|
|
63
|
+
scope_klass.send(custom_scope_name, operator, value)
|
|
64
|
+
else
|
|
65
|
+
@scope.where(arel_query)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def operator
|
|
70
|
+
oper = @query[:operator].to_sym
|
|
71
|
+
if OPERATORS[type].include?(oper)
|
|
72
|
+
oper
|
|
73
|
+
else
|
|
74
|
+
raise UnallowedOperator.new(@query), "Query (#{@query}) has unallowed operator: #{oper}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def attribute_name
|
|
79
|
+
(query_config[:attribute_name] || query_key).to_sym
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def value
|
|
83
|
+
val = query_config[:value].is_a?(Proc) ? query_config[:value].call(@query[:value]) : @query[:value]
|
|
84
|
+
return nil if val.nil? || val.to_s.strip.length == 0
|
|
85
|
+
case type
|
|
86
|
+
when :string
|
|
87
|
+
val.to_s
|
|
88
|
+
when :integer
|
|
89
|
+
val.to_s.include?(',') ? val.split(',').map(&:to_i) : val.to_i
|
|
90
|
+
when :enum
|
|
91
|
+
val.split(',').each do |v|
|
|
92
|
+
unless allowed_values.include?(v)
|
|
93
|
+
raise InvalidValue.new(@query), "Query (#{@query}) has invalid value: #{v}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
val
|
|
97
|
+
when :year
|
|
98
|
+
val.to_s
|
|
99
|
+
when :month
|
|
100
|
+
val.to_s
|
|
101
|
+
when :date, :date_future
|
|
102
|
+
Date.parse(val.to_s)
|
|
103
|
+
when :datetime
|
|
104
|
+
DateTime.parse(val.to_s)
|
|
105
|
+
when :boolean
|
|
106
|
+
if ['1', 'true', true].include?(val)
|
|
107
|
+
true
|
|
108
|
+
elsif ['0', 'false', false].include?(val)
|
|
109
|
+
false
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def value_for_arel
|
|
115
|
+
case type
|
|
116
|
+
when :string
|
|
117
|
+
if [:matches, :does_not_match].include?(operator)
|
|
118
|
+
"%#{value}%"
|
|
119
|
+
elsif [:begins_with].include?(operator)
|
|
120
|
+
"#{value}%"
|
|
121
|
+
elsif [:ends_with].include?(operator)
|
|
122
|
+
"%#{value}"
|
|
123
|
+
else
|
|
124
|
+
value
|
|
125
|
+
end
|
|
126
|
+
when :enum
|
|
127
|
+
if [:in, :not_in].include?(operator)
|
|
128
|
+
value.split(',')
|
|
129
|
+
else
|
|
130
|
+
value
|
|
131
|
+
end
|
|
132
|
+
else
|
|
133
|
+
value
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def type
|
|
138
|
+
t = query_config[:type].to_sym
|
|
139
|
+
unless SUPPORTED_TYPES.include?(t)
|
|
140
|
+
raise UnsupportedType.new(query_config), "Config (#{query_config}) has unsupported type: #{t}"
|
|
141
|
+
end
|
|
142
|
+
t
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def scope_klass
|
|
146
|
+
@scope_klass ||=
|
|
147
|
+
if query_config[:relation]
|
|
148
|
+
resolve_relation(@scope.klass, query_config[:relation])
|
|
149
|
+
else
|
|
150
|
+
@scope.klass
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def operator_for_arel
|
|
155
|
+
{
|
|
156
|
+
begins_with: :matches,
|
|
157
|
+
ends_with: :matches
|
|
158
|
+
}[operator] || operator
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
def resolve_relation(klass, relation)
|
|
163
|
+
if relation.is_a?(Hash)
|
|
164
|
+
resolve_relation(klass.reflect_on_association(relation.first.first).klass, relation.first.last)
|
|
165
|
+
elsif relation.is_a?(Symbol)
|
|
166
|
+
klass.reflect_on_association(relation).klass
|
|
167
|
+
else
|
|
168
|
+
raise
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def allowed_values
|
|
173
|
+
query_config[:values] || []
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def check_query_for_missing_keys!
|
|
177
|
+
if missing_required_keys.length > 0
|
|
178
|
+
raise MissingRequiredKey.new(@query), "Query (#{@query}) has missing keys: #{missing_required_keys}"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def missing_required_keys
|
|
183
|
+
(REQUIRED_KEYS - @query.keys.map(&:to_sym))
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def query_key
|
|
187
|
+
@query[:key].to_sym
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def custom_scope?
|
|
191
|
+
query_config[:scope].present?
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def custom_scope_name
|
|
195
|
+
if query_config[:scope].is_a?(Symbol) || query_config[:scope].is_a?(String)
|
|
196
|
+
query_config[:scope]
|
|
197
|
+
else
|
|
198
|
+
query_key
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def use_outer_join?
|
|
203
|
+
NEGATIVE_OPERATORS.include?(operator)
|
|
204
|
+
end
|
|
205
|
+
end
|