ransack 1.2.3 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -4
- data/CONTRIBUTING.md +12 -4
- data/Gemfile +4 -5
- data/README.md +160 -55
- data/lib/ransack.rb +1 -1
- data/lib/ransack/adapters/active_record/3.0/context.rb +16 -0
- data/lib/ransack/adapters/active_record/3.1/context.rb +24 -0
- data/lib/ransack/adapters/active_record/base.rb +6 -0
- data/lib/ransack/adapters/active_record/context.rb +49 -1
- data/lib/ransack/configuration.rb +23 -6
- data/lib/ransack/constants.rb +46 -45
- data/lib/ransack/context.rb +19 -2
- data/lib/ransack/helpers/form_builder.rb +5 -4
- data/lib/ransack/helpers/form_helper.rb +34 -14
- data/lib/ransack/locale/hu.yml +70 -0
- data/lib/ransack/locale/nl.yml +70 -0
- data/lib/ransack/nodes/attribute.rb +2 -2
- data/lib/ransack/nodes/condition.rb +29 -12
- data/lib/ransack/nodes/grouping.rb +6 -6
- data/lib/ransack/nodes/node.rb +1 -1
- data/lib/ransack/nodes/value.rb +1 -1
- data/lib/ransack/predicate.rb +4 -5
- data/lib/ransack/ransacker.rb +1 -1
- data/lib/ransack/search.rb +39 -13
- data/lib/ransack/translate.rb +7 -8
- data/lib/ransack/version.rb +1 -1
- data/ransack.gemspec +5 -5
- data/spec/ransack/adapters/active_record/base_spec.rb +78 -35
- data/spec/ransack/adapters/active_record/context_spec.rb +58 -15
- data/spec/ransack/configuration_spec.rb +18 -18
- data/spec/ransack/dependencies_spec.rb +1 -1
- data/spec/ransack/helpers/form_builder_spec.rb +29 -29
- data/spec/ransack/helpers/form_helper_spec.rb +14 -1
- data/spec/ransack/nodes/condition_spec.rb +21 -2
- data/spec/ransack/predicate_spec.rb +49 -9
- data/spec/ransack/search_spec.rb +178 -143
- data/spec/ransack/translate_spec.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/schema.rb +26 -21
- metadata +15 -11
@@ -73,6 +73,30 @@ module Ransack
|
|
73
73
|
@engine.connection_pool.columns_hash[table][name].type
|
74
74
|
end
|
75
75
|
|
76
|
+
def join_associations
|
77
|
+
@join_dependency.join_associations
|
78
|
+
end
|
79
|
+
|
80
|
+
# All dependent Arel::Join nodes used in the search query
|
81
|
+
#
|
82
|
+
# This could otherwise be done as `@object.arel.join_sources`, except
|
83
|
+
# that ActiveRecord's build_joins sets up its own JoinDependency.
|
84
|
+
# This extracts what we need to access the joins using our existing
|
85
|
+
# JoinDependency to track table aliases.
|
86
|
+
#
|
87
|
+
def join_sources
|
88
|
+
base = Arel::SelectManager.new(@object.engine, @object.table)
|
89
|
+
joins = @object.joins_values
|
90
|
+
joins.each do |assoc|
|
91
|
+
assoc.join_to(base)
|
92
|
+
end
|
93
|
+
base.join_sources
|
94
|
+
end
|
95
|
+
|
96
|
+
def alias_tracker
|
97
|
+
@join_dependency.alias_tracker
|
98
|
+
end
|
99
|
+
|
76
100
|
private
|
77
101
|
|
78
102
|
def get_parent_and_attribute_name(str, parent = @base)
|
@@ -12,6 +12,7 @@ module Ransack
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def ransack(params = {}, options = {})
|
15
|
+
params = params.presence || {}
|
15
16
|
Search.new(self, params ? params.delete_if {
|
16
17
|
|k, v| v.blank? && v != false } : params, options)
|
17
18
|
end
|
@@ -35,6 +36,11 @@ module Ransack
|
|
35
36
|
reflect_on_all_associations.map { |a| a.name.to_s }
|
36
37
|
end
|
37
38
|
|
39
|
+
# For overriding with a whitelist of symbols
|
40
|
+
def ransackable_scopes(auth_object = nil)
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
|
38
44
|
end
|
39
45
|
end
|
40
46
|
end
|
@@ -78,7 +78,55 @@ module Ransack
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
|
81
|
+
if ::ActiveRecord::VERSION::STRING >= '4.1'
|
82
|
+
|
83
|
+
def join_associations
|
84
|
+
raise NotImplementedError,
|
85
|
+
"ActiveRecord 4.1 and later does not use join_associations. Use join_sources."
|
86
|
+
end
|
87
|
+
|
88
|
+
# All dependent Arel::Join nodes used in the search query
|
89
|
+
#
|
90
|
+
# This could otherwise be done as `@object.arel.join_sources`, except
|
91
|
+
# that ActiveRecord's build_joins sets up its own JoinDependency.
|
92
|
+
# This extracts what we need to access the joins using our existing
|
93
|
+
# JoinDependency to track table aliases.
|
94
|
+
#
|
95
|
+
def join_sources
|
96
|
+
base = Arel::SelectManager.new(@object.engine, @object.table)
|
97
|
+
joins = @join_dependency.join_constraints(@object.joins_values)
|
98
|
+
joins.each do |aliased_join|
|
99
|
+
base.from(aliased_join)
|
100
|
+
end
|
101
|
+
base.join_sources
|
102
|
+
end
|
103
|
+
|
104
|
+
else
|
105
|
+
|
106
|
+
# All dependent JoinAssociation items used in the search query
|
107
|
+
#
|
108
|
+
# Deprecated: this goes away in ActiveRecord 4.1. Use join_sources.
|
109
|
+
#
|
110
|
+
def join_associations
|
111
|
+
@join_dependency.join_associations
|
112
|
+
end
|
113
|
+
|
114
|
+
def join_sources
|
115
|
+
base = Arel::SelectManager.new(@object.engine, @object.table)
|
116
|
+
joins = @object.joins_values
|
117
|
+
joins.each do |assoc|
|
118
|
+
assoc.join_to(base)
|
119
|
+
end
|
120
|
+
base.join_sources
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
def alias_tracker
|
126
|
+
@join_dependency.alias_tracker
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
82
130
|
|
83
131
|
def get_parent_and_attribute_name(str, parent = @base)
|
84
132
|
attr_name = nil
|
@@ -7,7 +7,8 @@ module Ransack
|
|
7
7
|
mattr_accessor :predicates, :options
|
8
8
|
self.predicates = {}
|
9
9
|
self.options = {
|
10
|
-
|
10
|
+
:search_key => :q,
|
11
|
+
:ignore_unknown_conditions => true
|
11
12
|
}
|
12
13
|
|
13
14
|
def configure
|
@@ -20,16 +21,18 @@ module Ransack
|
|
20
21
|
compounds = opts.delete(:compounds)
|
21
22
|
compounds = true if compounds.nil?
|
22
23
|
compounds = false if opts[:wants_array]
|
23
|
-
opts[:arel_predicate] = opts[:arel_predicate].to_s
|
24
24
|
|
25
25
|
self.predicates[name] = Predicate.new(opts)
|
26
26
|
|
27
27
|
['_any', '_all'].each do |suffix|
|
28
|
-
|
28
|
+
compound_name = name + suffix
|
29
|
+
self.predicates[compound_name] = Predicate.new(
|
29
30
|
opts.merge(
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
:name => compound_name,
|
32
|
+
:arel_predicate => arel_predicate_with_suffix(
|
33
|
+
opts[:arel_predicate], suffix
|
34
|
+
),
|
35
|
+
:compound => true
|
33
36
|
)
|
34
37
|
)
|
35
38
|
end if compounds
|
@@ -40,5 +43,19 @@ module Ransack
|
|
40
43
|
self.options[:search_key] = name
|
41
44
|
end
|
42
45
|
|
46
|
+
# raise an error if an unknown predicate, condition or attribute is passed
|
47
|
+
# into a search
|
48
|
+
def ignore_unknown_conditions=(boolean)
|
49
|
+
self.options[:ignore_unknown_conditions] = boolean
|
50
|
+
end
|
51
|
+
|
52
|
+
def arel_predicate_with_suffix(arel_predicate, suffix)
|
53
|
+
if arel_predicate === Proc
|
54
|
+
proc { |v| "#{arel_predicate.call(v)}#{suffix}" }
|
55
|
+
else
|
56
|
+
"#{arel_predicate}#{suffix}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
43
60
|
end
|
44
61
|
end
|
data/lib/ransack/constants.rb
CHANGED
@@ -2,97 +2,98 @@ module Ransack
|
|
2
2
|
module Constants
|
3
3
|
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
|
4
4
|
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
|
5
|
+
BOOLEAN_VALUES = TRUE_VALUES + FALSE_VALUES
|
5
6
|
|
6
7
|
AREL_PREDICATES = %w(eq not_eq matches does_not_match lt lteq gt gteq in not_in)
|
7
8
|
|
8
9
|
DERIVED_PREDICATES = [
|
9
10
|
['cont', {
|
10
|
-
arel_predicate
|
11
|
-
formatter
|
11
|
+
:arel_predicate => 'matches',
|
12
|
+
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
|
12
13
|
}
|
13
14
|
],
|
14
15
|
['not_cont', {
|
15
|
-
arel_predicate
|
16
|
-
formatter
|
16
|
+
:arel_predicate => 'does_not_match',
|
17
|
+
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
|
17
18
|
}
|
18
19
|
],
|
19
20
|
['start', {
|
20
|
-
arel_predicate
|
21
|
-
formatter
|
21
|
+
:arel_predicate => 'matches',
|
22
|
+
:formatter => proc { |v| "#{escape_wildcards(v)}%" }
|
22
23
|
}
|
23
24
|
],
|
24
25
|
['not_start', {
|
25
|
-
arel_predicate
|
26
|
-
formatter
|
26
|
+
:arel_predicate => 'does_not_match',
|
27
|
+
:formatter => proc { |v| "#{escape_wildcards(v)}%" }
|
27
28
|
}
|
28
29
|
],
|
29
30
|
['end', {
|
30
|
-
arel_predicate
|
31
|
-
formatter
|
31
|
+
:arel_predicate => 'matches',
|
32
|
+
:formatter => proc { |v| "%#{escape_wildcards(v)}" }
|
32
33
|
}
|
33
34
|
],
|
34
35
|
['not_end', {
|
35
|
-
arel_predicate
|
36
|
-
formatter
|
36
|
+
:arel_predicate => 'does_not_match',
|
37
|
+
:formatter => proc { |v| "%#{escape_wildcards(v)}" }
|
37
38
|
}
|
38
39
|
],
|
39
40
|
['true', {
|
40
|
-
arel_predicate
|
41
|
-
compounds
|
42
|
-
type
|
43
|
-
validator
|
41
|
+
:arel_predicate => 'eq',
|
42
|
+
:compounds => false,
|
43
|
+
:type => :boolean,
|
44
|
+
:validator => proc { |v| TRUE_VALUES.include?(v) }
|
44
45
|
}
|
45
46
|
],
|
46
47
|
['false', {
|
47
|
-
arel_predicate
|
48
|
-
compounds
|
49
|
-
type
|
50
|
-
validator
|
51
|
-
formatter
|
48
|
+
:arel_predicate => 'eq',
|
49
|
+
:compounds => false,
|
50
|
+
:type => :boolean,
|
51
|
+
:validator => proc { |v| TRUE_VALUES.include?(v) },
|
52
|
+
:formatter => proc { |v| !v }
|
52
53
|
}
|
53
54
|
],
|
54
55
|
['present', {
|
55
|
-
arel_predicate
|
56
|
-
compounds
|
57
|
-
type
|
58
|
-
validator
|
59
|
-
formatter
|
56
|
+
:arel_predicate => proc { |v| v ? 'not_eq_all' : 'eq_any' },
|
57
|
+
:compounds => false,
|
58
|
+
:type => :boolean,
|
59
|
+
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
|
60
|
+
:formatter => proc { |v| [nil, ''] }
|
60
61
|
}
|
61
62
|
],
|
62
63
|
['blank', {
|
63
|
-
arel_predicate
|
64
|
-
compounds
|
65
|
-
type
|
66
|
-
validator
|
67
|
-
formatter
|
64
|
+
:arel_predicate => proc { |v| v ? 'eq_any' : 'not_eq_all' },
|
65
|
+
:compounds => false,
|
66
|
+
:type => :boolean,
|
67
|
+
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
|
68
|
+
:formatter => proc { |v| [nil, ''] }
|
68
69
|
}
|
69
70
|
],
|
70
71
|
['null', {
|
71
|
-
arel_predicate
|
72
|
-
compounds
|
73
|
-
type
|
74
|
-
validator
|
75
|
-
formatter
|
72
|
+
:arel_predicate => proc { |v| v ? 'eq' : 'not_eq' },
|
73
|
+
:compounds => false,
|
74
|
+
:type => :boolean,
|
75
|
+
:validator => proc { |v| BOOLEAN_VALUES.include?(v)},
|
76
|
+
:formatter => proc { |v| nil }
|
76
77
|
}
|
77
78
|
],
|
78
79
|
['not_null', {
|
79
|
-
arel_predicate
|
80
|
-
compounds
|
81
|
-
type
|
82
|
-
validator
|
83
|
-
formatter
|
80
|
+
:arel_predicate => proc { |v| v ? 'not_eq' : 'eq' },
|
81
|
+
:compounds => false,
|
82
|
+
:type => :boolean,
|
83
|
+
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
|
84
|
+
:formatter => proc { |v| nil } }
|
84
85
|
]
|
85
86
|
]
|
86
87
|
|
87
|
-
|
88
|
+
module_function
|
88
89
|
# replace % \ to \% \\
|
89
90
|
def escape_wildcards(unescaped)
|
90
91
|
case ActiveRecord::Base.connection.adapter_name
|
91
|
-
when "
|
92
|
-
unescaped
|
93
|
-
else
|
92
|
+
when "Mysql2", "PostgreSQL"
|
94
93
|
# Necessary for PostgreSQL and MySQL
|
95
94
|
unescaped.to_s.gsub(/([\\|\%|.])/, '\\\\\\1')
|
95
|
+
else
|
96
|
+
unescaped
|
96
97
|
end
|
97
98
|
end
|
98
99
|
end
|
data/lib/ransack/context.rb
CHANGED
@@ -2,7 +2,7 @@ require 'ransack/visitor'
|
|
2
2
|
|
3
3
|
module Ransack
|
4
4
|
class Context
|
5
|
-
attr_reader :
|
5
|
+
attr_reader :object, :klass, :base, :engine, :arel_visitor
|
6
6
|
attr_accessor :auth_object, :search_key
|
7
7
|
|
8
8
|
class << self
|
@@ -46,7 +46,7 @@ module Ransack
|
|
46
46
|
end
|
47
47
|
|
48
48
|
@default_table = Arel::Table.new(
|
49
|
-
@base.table_name, as
|
49
|
+
@base.table_name, :as => @base.aliased_table_name, :engine => @engine
|
50
50
|
)
|
51
51
|
@bind_pairs = Hash.new do |hash, key|
|
52
52
|
parent, attr_name = get_parent_and_attribute_name(key.to_s)
|
@@ -77,6 +77,19 @@ module Ransack
|
|
77
77
|
table_for(parent)[attr_name]
|
78
78
|
end
|
79
79
|
|
80
|
+
def chain_scope(scope, args)
|
81
|
+
return unless @klass.method(scope) && args != false
|
82
|
+
@object = if scope_arity(scope) < 1 && args == true
|
83
|
+
@object.public_send(scope)
|
84
|
+
else
|
85
|
+
@object.public_send(scope, *args)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def scope_arity(scope)
|
90
|
+
@klass.method(scope).arity
|
91
|
+
end
|
92
|
+
|
80
93
|
def bind(object, str)
|
81
94
|
object.parent, object.attr_name = @bind_pairs[str]
|
82
95
|
end
|
@@ -143,6 +156,10 @@ module Ransack
|
|
143
156
|
klass.ransackable_associations(auth_object).include? str
|
144
157
|
end
|
145
158
|
|
159
|
+
def ransackable_scope?(str, klass)
|
160
|
+
klass.ransackable_scopes(auth_object).any? { |s| s.to_s == str }
|
161
|
+
end
|
162
|
+
|
146
163
|
def searchable_attributes(str = '')
|
147
164
|
traverse(str).ransackable_attributes(auth_object)
|
148
165
|
end
|
@@ -13,7 +13,7 @@ module Ransack
|
|
13
13
|
text = args.first
|
14
14
|
i18n = options[:i18n] || {}
|
15
15
|
text ||= object.translate(
|
16
|
-
method, i18n.reverse_merge(include_associations
|
16
|
+
method, i18n.reverse_merge(:include_associations => true)
|
17
17
|
) if object.respond_to? :translate
|
18
18
|
super(method, text, options, &block)
|
19
19
|
end
|
@@ -127,7 +127,8 @@ module Ransack
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def combinator_select(options = {}, html_options = {})
|
130
|
-
template_collection_select(
|
130
|
+
template_collection_select(
|
131
|
+
:m, combinator_choices, options, html_options)
|
131
132
|
end
|
132
133
|
|
133
134
|
private
|
@@ -202,7 +203,7 @@ module Ransack
|
|
202
203
|
|
203
204
|
def get_attribute_element(action, base)
|
204
205
|
begin
|
205
|
-
[Translate.association(base, context
|
206
|
+
[Translate.association(base, :context => object.context),
|
206
207
|
collection_for_base(action, base)]
|
207
208
|
rescue UntraversableAssociationError => e
|
208
209
|
nil
|
@@ -214,7 +215,7 @@ module Ransack
|
|
214
215
|
[attr_from_base_and_column(base, c),
|
215
216
|
Translate.attribute(
|
216
217
|
attr_from_base_and_column(base, c),
|
217
|
-
context
|
218
|
+
:context => object.context
|
218
219
|
)
|
219
220
|
]
|
220
221
|
end
|
@@ -2,6 +2,26 @@ module Ransack
|
|
2
2
|
module Helpers
|
3
3
|
module FormHelper
|
4
4
|
|
5
|
+
def asc
|
6
|
+
'asc'.freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def desc
|
10
|
+
'desc'.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def asc_arrow
|
14
|
+
'▲'.freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def desc_arrow
|
18
|
+
'▼'.freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
def non_breaking_space
|
22
|
+
' '.freeze
|
23
|
+
end
|
24
|
+
|
5
25
|
def search_form_for(record, options = {}, &proc)
|
6
26
|
if record.is_a?(Ransack::Search)
|
7
27
|
search = record
|
@@ -20,15 +40,15 @@ module Ransack
|
|
20
40
|
end
|
21
41
|
options[:html] ||= {}
|
22
42
|
html_options = {
|
23
|
-
class
|
43
|
+
:class => options[:class].present? ?
|
24
44
|
"#{options[:class]}" :
|
25
45
|
"#{search.klass.to_s.underscore}_search",
|
26
|
-
id
|
46
|
+
:id => options[:id].present? ?
|
27
47
|
"#{options[:id]}" :
|
28
48
|
"#{search.klass.to_s.underscore}_search",
|
29
|
-
method
|
49
|
+
:method => :get
|
30
50
|
}
|
31
|
-
options[:as] ||= 'q'
|
51
|
+
options[:as] ||= 'q'.freeze
|
32
52
|
options[:html].reverse_merge!(html_options)
|
33
53
|
options[:builder] ||= FormBuilder
|
34
54
|
|
@@ -45,7 +65,7 @@ module Ransack
|
|
45
65
|
raise TypeError, "First argument must be a Ransack::Search!" unless
|
46
66
|
Search === search
|
47
67
|
|
48
|
-
search_params = params[search.context.search_key] ||
|
68
|
+
search_params = params[search.context.search_key].presence ||
|
49
69
|
{}.with_indifferent_access
|
50
70
|
|
51
71
|
attr_name = attribute.to_s
|
@@ -54,7 +74,7 @@ module Ransack
|
|
54
74
|
if args.size > 0 && !args.first.is_a?(Hash)
|
55
75
|
args.shift.to_s
|
56
76
|
else
|
57
|
-
Translate.attribute(attr_name, context
|
77
|
+
Translate.attribute(attr_name, :context => search.context)
|
58
78
|
end
|
59
79
|
)
|
60
80
|
|
@@ -67,9 +87,9 @@ module Ransack
|
|
67
87
|
current_dir = prev_attr == attr_name ? prev_dir : nil
|
68
88
|
|
69
89
|
if current_dir
|
70
|
-
new_dir = current_dir ==
|
90
|
+
new_dir = current_dir == desc ? asc : desc
|
71
91
|
else
|
72
|
-
new_dir = default_order ||
|
92
|
+
new_dir = default_order || asc
|
73
93
|
end
|
74
94
|
|
75
95
|
html_options = args.first.is_a?(Hash) ? args.shift.dup : {}
|
@@ -77,7 +97,7 @@ module Ransack
|
|
77
97
|
html_options[:class] = [css, html_options[:class]].compact.join(' ')
|
78
98
|
query_hash = {}
|
79
99
|
query_hash[search.context.search_key] = search_params
|
80
|
-
|
100
|
+
.merge(:s => "#{attr_name} #{new_dir}")
|
81
101
|
options.merge!(query_hash)
|
82
102
|
options_for_url = params.merge options
|
83
103
|
|
@@ -90,7 +110,7 @@ module Ransack
|
|
90
110
|
link_to(
|
91
111
|
[ERB::Util.h(name), order_indicator_for(current_dir)]
|
92
112
|
.compact
|
93
|
-
.join(
|
113
|
+
.join(non_breaking_space)
|
94
114
|
.html_safe,
|
95
115
|
url,
|
96
116
|
html_options
|
@@ -100,10 +120,10 @@ module Ransack
|
|
100
120
|
private
|
101
121
|
|
102
122
|
def order_indicator_for(order)
|
103
|
-
if order ==
|
104
|
-
|
105
|
-
elsif order ==
|
106
|
-
|
123
|
+
if order == asc
|
124
|
+
asc_arrow
|
125
|
+
elsif order == desc
|
126
|
+
desc_arrow
|
107
127
|
else
|
108
128
|
nil
|
109
129
|
end
|