ransack_ffcrm 0.6.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.
- data/.gitignore +4 -0
- data/.travis.yml +9 -0
- data/Gemfile +40 -0
- data/LICENSE +20 -0
- data/README.md +137 -0
- data/Rakefile +19 -0
- data/lib/ransack/adapters/active_record/3.0/compat.rb +166 -0
- data/lib/ransack/adapters/active_record/3.0/context.rb +161 -0
- data/lib/ransack/adapters/active_record/3.1/context.rb +166 -0
- data/lib/ransack/adapters/active_record/base.rb +33 -0
- data/lib/ransack/adapters/active_record/context.rb +41 -0
- data/lib/ransack/adapters/active_record.rb +12 -0
- data/lib/ransack/configuration.rb +35 -0
- data/lib/ransack/constants.rb +23 -0
- data/lib/ransack/context.rb +124 -0
- data/lib/ransack/helpers/form_builder.rb +203 -0
- data/lib/ransack/helpers/form_helper.rb +75 -0
- data/lib/ransack/helpers.rb +2 -0
- data/lib/ransack/locale/en.yml +70 -0
- data/lib/ransack/naming.rb +53 -0
- data/lib/ransack/nodes/attribute.rb +49 -0
- data/lib/ransack/nodes/bindable.rb +30 -0
- data/lib/ransack/nodes/condition.rb +212 -0
- data/lib/ransack/nodes/grouping.rb +183 -0
- data/lib/ransack/nodes/node.rb +34 -0
- data/lib/ransack/nodes/sort.rb +41 -0
- data/lib/ransack/nodes/value.rb +108 -0
- data/lib/ransack/nodes.rb +7 -0
- data/lib/ransack/predicate.rb +70 -0
- data/lib/ransack/ransacker.rb +24 -0
- data/lib/ransack/search.rb +123 -0
- data/lib/ransack/translate.rb +92 -0
- data/lib/ransack/version.rb +3 -0
- data/lib/ransack/visitor.rb +68 -0
- data/lib/ransack.rb +27 -0
- data/ransack_ffcrm.gemspec +30 -0
- data/spec/blueprints/articles.rb +5 -0
- data/spec/blueprints/comments.rb +5 -0
- data/spec/blueprints/notes.rb +3 -0
- data/spec/blueprints/people.rb +4 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/console.rb +21 -0
- data/spec/helpers/ransack_helper.rb +2 -0
- data/spec/ransack/adapters/active_record/base_spec.rb +67 -0
- data/spec/ransack/adapters/active_record/context_spec.rb +45 -0
- data/spec/ransack/configuration_spec.rb +31 -0
- data/spec/ransack/helpers/form_builder_spec.rb +137 -0
- data/spec/ransack/helpers/form_helper_spec.rb +38 -0
- data/spec/ransack/nodes/condition_spec.rb +15 -0
- data/spec/ransack/nodes/grouping_spec.rb +13 -0
- data/spec/ransack/predicate_spec.rb +55 -0
- data/spec/ransack/search_spec.rb +225 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/en.yml +5 -0
- data/spec/support/schema.rb +111 -0
- metadata +229 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Helpers
|
3
|
+
module FormHelper
|
4
|
+
|
5
|
+
def search_form_for(record, options = {}, &proc)
|
6
|
+
if record.is_a?(Ransack::Search)
|
7
|
+
search = record
|
8
|
+
options[:url] ||= polymorphic_path(search.klass)
|
9
|
+
elsif record.is_a?(Array) && (search = record.detect {|o| o.is_a?(Ransack::Search)})
|
10
|
+
options[:url] ||= polymorphic_path(record.map {|o| o.is_a?(Ransack::Search) ? o.klass : o})
|
11
|
+
else
|
12
|
+
raise ArgumentError, "No Ransack::Search object was provided to search_form_for!"
|
13
|
+
end
|
14
|
+
options[:html] ||= {}
|
15
|
+
html_options = {
|
16
|
+
:class => options[:as] ? "#{options[:as]}_search" : "#{search.klass.to_s.underscore}_search",
|
17
|
+
:id => options[:as] ? "#{options[:as]}_search" : "#{search.klass.to_s.underscore}_search",
|
18
|
+
:method => :get
|
19
|
+
}
|
20
|
+
options[:as] ||= 'q'
|
21
|
+
options[:html].reverse_merge!(html_options)
|
22
|
+
options[:builder] ||= FormBuilder
|
23
|
+
|
24
|
+
form_for(record, options, &proc)
|
25
|
+
end
|
26
|
+
|
27
|
+
def sort_link(search, attribute, *args)
|
28
|
+
raise TypeError, "First argument must be a Ransack::Search!" unless Search === search
|
29
|
+
|
30
|
+
search_params = params[:q] || {}.with_indifferent_access
|
31
|
+
|
32
|
+
attr_name = attribute.to_s
|
33
|
+
|
34
|
+
name = (args.size > 0 && !args.first.is_a?(Hash)) ? args.shift.to_s : Translate.attribute(attr_name, :context => search.context)
|
35
|
+
|
36
|
+
if existing_sort = search.sorts.detect {|s| s.name == attr_name}
|
37
|
+
prev_attr, prev_dir = existing_sort.name, existing_sort.dir
|
38
|
+
end
|
39
|
+
|
40
|
+
options = args.first.is_a?(Hash) ? args.shift.dup : {}
|
41
|
+
default_order = options.delete :default_order
|
42
|
+
current_dir = prev_attr == attr_name ? prev_dir : nil
|
43
|
+
|
44
|
+
if current_dir
|
45
|
+
new_dir = current_dir == 'desc' ? 'asc' : 'desc'
|
46
|
+
else
|
47
|
+
new_dir = default_order || 'asc'
|
48
|
+
end
|
49
|
+
|
50
|
+
html_options = args.first.is_a?(Hash) ? args.shift.dup : {}
|
51
|
+
css = ['sort_link', current_dir].compact.join(' ')
|
52
|
+
html_options[:class] = [css, html_options[:class]].compact.join(' ')
|
53
|
+
options.merge!(
|
54
|
+
:q => search_params.merge(:s => "#{attr_name} #{new_dir}")
|
55
|
+
)
|
56
|
+
link_to [ERB::Util.h(name), order_indicator_for(current_dir)].compact.join(' ').html_safe,
|
57
|
+
url_for(options),
|
58
|
+
html_options
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def order_indicator_for(order)
|
64
|
+
if order == 'asc'
|
65
|
+
'▲'
|
66
|
+
elsif order == 'desc'
|
67
|
+
'▼'
|
68
|
+
else
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
en:
|
2
|
+
ransack:
|
3
|
+
search: "search"
|
4
|
+
predicate: "predicate"
|
5
|
+
and: "and"
|
6
|
+
or: "or"
|
7
|
+
any: "any"
|
8
|
+
all: "all"
|
9
|
+
combinator: "combinator"
|
10
|
+
attribute: "attribute"
|
11
|
+
value: "value"
|
12
|
+
condition: "condition"
|
13
|
+
sort: "sort"
|
14
|
+
asc: "ascending"
|
15
|
+
desc: "descending"
|
16
|
+
predicates:
|
17
|
+
eq: "equals"
|
18
|
+
eq_any: "equals any"
|
19
|
+
eq_all: "equals all"
|
20
|
+
not_eq: "not equal to"
|
21
|
+
not_eq_any: "not equal to any"
|
22
|
+
not_eq_all: "not equal to all"
|
23
|
+
matches: "matches"
|
24
|
+
matches_any: "matches_any"
|
25
|
+
matches_all: "matches all"
|
26
|
+
does_not_match: "doesn't match"
|
27
|
+
does_not_match_any: "doesn't match any"
|
28
|
+
does_not_match_all: "doesn't match all"
|
29
|
+
lt: "less than"
|
30
|
+
lt_any: "less than any"
|
31
|
+
lt_all: "less than all"
|
32
|
+
lteq: "less than or equal to"
|
33
|
+
lteq_any: "less than or equal to any"
|
34
|
+
lteq_all: "less than or equal to all"
|
35
|
+
gt: "greater than"
|
36
|
+
gt_any: "greater than any"
|
37
|
+
gt_all: "greater than all"
|
38
|
+
gteq: "greater than or equal to"
|
39
|
+
gteq_any: "greater than or equal to any"
|
40
|
+
gteq_all: "greater than or equal to all"
|
41
|
+
in: "in"
|
42
|
+
in_any: "in any"
|
43
|
+
in_all: "in all"
|
44
|
+
not_in: "not in"
|
45
|
+
not_in_any: "not in any"
|
46
|
+
not_in_all: "not in all"
|
47
|
+
cont: "contains"
|
48
|
+
cont_any: "contains any"
|
49
|
+
cont_all: "contains all"
|
50
|
+
not_cont: "doesn't contain"
|
51
|
+
not_cont_any: "doesn't contain any"
|
52
|
+
not_cont_all: "doesn't contain all"
|
53
|
+
start: "starts with"
|
54
|
+
start_any: "starts with any"
|
55
|
+
start_all: "starts with all"
|
56
|
+
not_start: "doesn't start with"
|
57
|
+
not_start_any: "doesn't start with any"
|
58
|
+
not_start_all: "doesn't start with all"
|
59
|
+
end: "ends with"
|
60
|
+
end_any: "ends with any"
|
61
|
+
end_all: "ends with all"
|
62
|
+
not_end: "doesn't end with"
|
63
|
+
not_end_any: "doesn't end with any"
|
64
|
+
not_end_all: "doesn't end with all"
|
65
|
+
'true': "is true"
|
66
|
+
'false': "is false"
|
67
|
+
present: "is present"
|
68
|
+
blank: "is blank"
|
69
|
+
'null': "is null"
|
70
|
+
not_null: "is not null"
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Naming
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
def persisted?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_key
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_param
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_model
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Name < String
|
26
|
+
attr_reader :singular, :plural, :element, :collection, :partial_path, :human, :param_key, :route_key, :i18n_key
|
27
|
+
alias_method :cache_key, :collection
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
super("Search")
|
31
|
+
@singular = "search".freeze
|
32
|
+
@plural = "searches".freeze
|
33
|
+
@element = "search".freeze
|
34
|
+
@human = "Search".freeze
|
35
|
+
@collection = "ransack/searches".freeze
|
36
|
+
@partial_path = "#{@collection}/#{@element}".freeze
|
37
|
+
@param_key = "q".freeze
|
38
|
+
@route_key = "searches".freeze
|
39
|
+
@i18n_key = :ransack
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
def model_name
|
45
|
+
@_model_name ||= Name.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def i18n_scope
|
49
|
+
:ransack
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
class Attribute < Node
|
4
|
+
include Bindable
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
delegate :blank?, :present?, :==, :to => :name
|
9
|
+
delegate :engine, :to => :context
|
10
|
+
|
11
|
+
def initialize(context, name = nil)
|
12
|
+
super(context)
|
13
|
+
self.name = name unless name.blank?
|
14
|
+
end
|
15
|
+
|
16
|
+
def name=(name)
|
17
|
+
@name = name
|
18
|
+
context.bind(self, name) unless name.blank?
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
bound? && attr
|
23
|
+
end
|
24
|
+
|
25
|
+
def type
|
26
|
+
if ransacker
|
27
|
+
return ransacker.type
|
28
|
+
else
|
29
|
+
context.type_for(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def eql?(other)
|
34
|
+
self.class == other.class &&
|
35
|
+
self.name == other.name
|
36
|
+
end
|
37
|
+
alias :== :eql?
|
38
|
+
|
39
|
+
def hash
|
40
|
+
self.name.hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def persisted?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
module Bindable
|
4
|
+
|
5
|
+
attr_accessor :parent, :attr_name
|
6
|
+
|
7
|
+
def attr
|
8
|
+
@attr ||= ransacker ? ransacker.attr_from(self) : context.table_for(parent)[attr_name]
|
9
|
+
end
|
10
|
+
alias :arel_attribute :attr
|
11
|
+
|
12
|
+
def ransacker
|
13
|
+
klass._ransackers[attr_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def klass
|
17
|
+
@klass ||= context.klassify(parent)
|
18
|
+
end
|
19
|
+
|
20
|
+
def bound?
|
21
|
+
attr_name.present? && parent.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset_binding!
|
25
|
+
@parent = @attr_name = @attr = @klass = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
class Condition < Node
|
4
|
+
i18n_word :attribute, :predicate, :combinator, :value
|
5
|
+
i18n_alias :a => :attribute, :p => :predicate, :m => :combinator, :v => :value
|
6
|
+
|
7
|
+
attr_accessor :predicate
|
8
|
+
attr_writer :is_default
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def extract(context, key, values)
|
12
|
+
attributes, predicate = extract_attributes_and_predicate(key)
|
13
|
+
if attributes.size > 0
|
14
|
+
combinator = key.match(/_(or|and)_/) ? $1 : nil
|
15
|
+
condition = self.new(context)
|
16
|
+
condition.build(
|
17
|
+
:a => attributes,
|
18
|
+
:p => predicate.name,
|
19
|
+
:m => combinator,
|
20
|
+
:v => predicate.wants_array ? Array(values) : [values]
|
21
|
+
)
|
22
|
+
# TODO: Figure out what to do with multiple types of attributes, if anything.
|
23
|
+
# Tempted to go with "garbage in, garbage out" on this one
|
24
|
+
predicate.validate(condition.values, condition.default_type) ? condition : nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def extract_attributes_and_predicate(key)
|
31
|
+
str = key.dup
|
32
|
+
name = Predicate.detect_and_strip_from_string!(str)
|
33
|
+
predicate = Predicate.named(name)
|
34
|
+
raise ArgumentError, "No valid predicate for #{key}" unless predicate
|
35
|
+
attributes = str.split(/_and_|_or_/)
|
36
|
+
[attributes, predicate]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid?
|
41
|
+
attributes.detect(&:valid?) && predicate && valid_arity? && predicate.validate(values, default_type) && valid_combinator?
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid_arity?
|
45
|
+
values.size <= 1 || predicate.wants_array
|
46
|
+
end
|
47
|
+
|
48
|
+
def attributes
|
49
|
+
@attributes ||= []
|
50
|
+
end
|
51
|
+
alias :a :attributes
|
52
|
+
|
53
|
+
def attributes=(args)
|
54
|
+
case args
|
55
|
+
when Array
|
56
|
+
args.each do |attr|
|
57
|
+
attr = Attribute.new(@context, attr)
|
58
|
+
self.attributes << attr if attr.valid?
|
59
|
+
end
|
60
|
+
when Hash
|
61
|
+
args.each do |index, attrs|
|
62
|
+
attr = Attribute.new(@context, attrs[:name])
|
63
|
+
self.attributes << attr if attr.valid?
|
64
|
+
end
|
65
|
+
else
|
66
|
+
raise ArgumentError, "Invalid argument (#{args.class}) supplied to attributes="
|
67
|
+
end
|
68
|
+
end
|
69
|
+
alias :a= :attributes=
|
70
|
+
|
71
|
+
def values
|
72
|
+
@values ||= []
|
73
|
+
end
|
74
|
+
alias :v :values
|
75
|
+
|
76
|
+
def values=(args)
|
77
|
+
case args
|
78
|
+
when Array
|
79
|
+
args.each do |val|
|
80
|
+
val = Value.new(@context, val)
|
81
|
+
self.values << val
|
82
|
+
end
|
83
|
+
when Hash
|
84
|
+
args.each do |index, attrs|
|
85
|
+
val = Value.new(@context, attrs[:value])
|
86
|
+
self.values << val
|
87
|
+
end
|
88
|
+
else
|
89
|
+
raise ArgumentError, "Invalid argument (#{args.class}) supplied to values="
|
90
|
+
end
|
91
|
+
end
|
92
|
+
alias :v= :values=
|
93
|
+
|
94
|
+
def combinator
|
95
|
+
@attributes.size > 1 ? @combinator : nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def combinator=(val)
|
99
|
+
@combinator = ['and', 'or'].detect {|v| v == val.to_s} || nil
|
100
|
+
end
|
101
|
+
alias :m= :combinator=
|
102
|
+
alias :m :combinator
|
103
|
+
|
104
|
+
def build_attribute(name = nil)
|
105
|
+
Attribute.new(@context, name).tap do |attribute|
|
106
|
+
self.attributes << attribute
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_value(val = nil)
|
111
|
+
Value.new(@context, val).tap do |value|
|
112
|
+
self.values << value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def value
|
117
|
+
predicate.wants_array ? values.map {|v| v.cast(default_type)} : values.first.cast(default_type)
|
118
|
+
end
|
119
|
+
|
120
|
+
def build(params)
|
121
|
+
params.with_indifferent_access.each do |key, value|
|
122
|
+
if key.match(/^(a|v|p|m)$/)
|
123
|
+
self.send("#{key}=", value)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
def persisted?
|
131
|
+
false
|
132
|
+
end
|
133
|
+
|
134
|
+
def default?
|
135
|
+
@is_default
|
136
|
+
end
|
137
|
+
|
138
|
+
def key
|
139
|
+
@key ||= attributes.map(&:name).join("_#{combinator}_") + "_#{predicate.name}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def eql?(other)
|
143
|
+
self.class == other.class &&
|
144
|
+
self.attributes == other.attributes &&
|
145
|
+
self.predicate == other.predicate &&
|
146
|
+
self.values == other.values &&
|
147
|
+
self.combinator == other.combinator
|
148
|
+
end
|
149
|
+
alias :== :eql?
|
150
|
+
|
151
|
+
def hash
|
152
|
+
[attributes, predicate, values, combinator].hash
|
153
|
+
end
|
154
|
+
|
155
|
+
def predicate_name=(name)
|
156
|
+
self.predicate = Predicate.named(name)
|
157
|
+
end
|
158
|
+
alias :p= :predicate_name=
|
159
|
+
|
160
|
+
def predicate_name
|
161
|
+
predicate.name if predicate
|
162
|
+
end
|
163
|
+
alias :p :predicate_name
|
164
|
+
|
165
|
+
def arel_predicate
|
166
|
+
predicates = attributes.map do |attr|
|
167
|
+
attr.attr.send(predicate.arel_predicate, formatted_values_for_attribute(attr))
|
168
|
+
end
|
169
|
+
|
170
|
+
if predicates.size > 1
|
171
|
+
case combinator
|
172
|
+
when 'and'
|
173
|
+
Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates))
|
174
|
+
when 'or'
|
175
|
+
predicates.inject(&:or)
|
176
|
+
end
|
177
|
+
else
|
178
|
+
predicates.first
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def validated_values
|
183
|
+
values.select {|v| predicate.validator.call(v.value)}
|
184
|
+
end
|
185
|
+
|
186
|
+
def casted_values_for_attribute(attr)
|
187
|
+
validated_values.map {|v| v.cast(predicate.type || attr.type)}
|
188
|
+
end
|
189
|
+
|
190
|
+
def formatted_values_for_attribute(attr)
|
191
|
+
formatted = casted_values_for_attribute(attr).map do |val|
|
192
|
+
val = attr.ransacker.formatter.call(val) if attr.ransacker && attr.ransacker.formatter
|
193
|
+
val = predicate.format(val)
|
194
|
+
val
|
195
|
+
end
|
196
|
+
predicate.wants_array ? formatted : formatted.first
|
197
|
+
end
|
198
|
+
|
199
|
+
def default_type
|
200
|
+
predicate.type || (attributes.first && attributes.first.type)
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
def valid_combinator?
|
206
|
+
attributes.size < 2 ||
|
207
|
+
['and', 'or'].include?(combinator)
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
class Grouping < Node
|
4
|
+
attr_reader :conditions
|
5
|
+
attr_accessor :combinator
|
6
|
+
alias :m :combinator
|
7
|
+
alias :m= :combinator=
|
8
|
+
|
9
|
+
i18n_word :condition, :and, :or
|
10
|
+
i18n_alias :c => :condition, :n => :and, :o => :or
|
11
|
+
|
12
|
+
delegate :each, :to => :values
|
13
|
+
|
14
|
+
def initialize(context, combinator = nil)
|
15
|
+
super(context)
|
16
|
+
self.combinator = combinator.to_s if combinator
|
17
|
+
end
|
18
|
+
|
19
|
+
def persisted?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def translate(key, options = {})
|
24
|
+
super or Translate.attribute(key.to_s, options.merge(:context => context))
|
25
|
+
end
|
26
|
+
|
27
|
+
def conditions
|
28
|
+
@conditions ||= []
|
29
|
+
end
|
30
|
+
alias :c :conditions
|
31
|
+
|
32
|
+
def conditions=(conditions)
|
33
|
+
case conditions
|
34
|
+
when Array
|
35
|
+
conditions.each do |attrs|
|
36
|
+
condition = Condition.new(@context).build(attrs)
|
37
|
+
self.conditions << condition if condition.valid?
|
38
|
+
end
|
39
|
+
when Hash
|
40
|
+
conditions.each do |index, attrs|
|
41
|
+
condition = Condition.new(@context).build(attrs)
|
42
|
+
self.conditions << condition if condition.valid?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
self.conditions.uniq!
|
47
|
+
end
|
48
|
+
alias :c= :conditions=
|
49
|
+
|
50
|
+
def [](key)
|
51
|
+
if condition = conditions.detect {|c| c.key == key.to_s}
|
52
|
+
condition
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def []=(key, value)
|
59
|
+
conditions.reject! {|c| c.key == key.to_s}
|
60
|
+
self.conditions << value
|
61
|
+
end
|
62
|
+
|
63
|
+
def values
|
64
|
+
conditions + groupings
|
65
|
+
end
|
66
|
+
|
67
|
+
def respond_to?(method_id)
|
68
|
+
super or begin
|
69
|
+
method_name = method_id.to_s
|
70
|
+
writer = method_name.sub!(/\=$/, '')
|
71
|
+
attribute_method?(method_name) ? true : false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_condition(opts = {})
|
76
|
+
new_condition(opts).tap do |condition|
|
77
|
+
self.conditions << condition
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def new_condition(opts = {})
|
82
|
+
attrs = opts[:attributes] || 1
|
83
|
+
vals = opts[:values] || 1
|
84
|
+
condition = Condition.new(@context)
|
85
|
+
condition.predicate_name = opts[:predicate] || 'eq'
|
86
|
+
condition.is_default = true
|
87
|
+
attrs.times { condition.build_attribute }
|
88
|
+
vals.times { condition.build_value }
|
89
|
+
condition
|
90
|
+
end
|
91
|
+
|
92
|
+
def groupings
|
93
|
+
@groupings ||= []
|
94
|
+
end
|
95
|
+
alias :g :groupings
|
96
|
+
|
97
|
+
def groupings=(groupings)
|
98
|
+
case groupings
|
99
|
+
when Array
|
100
|
+
groupings.each do |attrs|
|
101
|
+
grouping_object = new_grouping(attrs)
|
102
|
+
self.groupings << grouping_object if grouping_object.values.any?
|
103
|
+
end
|
104
|
+
when Hash
|
105
|
+
groupings.each do |index, attrs|
|
106
|
+
grouping_object = new_grouping(attrs)
|
107
|
+
self.groupings << grouping_object if grouping_object.values.any?
|
108
|
+
end
|
109
|
+
else
|
110
|
+
raise ArgumentError, "Invalid argument (#{groupings.class}) supplied to groupings="
|
111
|
+
end
|
112
|
+
end
|
113
|
+
alias :g= :groupings=
|
114
|
+
|
115
|
+
def method_missing(method_id, *args)
|
116
|
+
method_name = method_id.to_s
|
117
|
+
writer = method_name.sub!(/\=$/, '')
|
118
|
+
if attribute_method?(method_name)
|
119
|
+
writer ? write_attribute(method_name, *args) : read_attribute(method_name)
|
120
|
+
else
|
121
|
+
super
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def attribute_method?(name)
|
126
|
+
name = strip_predicate_and_index(name)
|
127
|
+
case name
|
128
|
+
when /^(g|c|m|groupings|conditions|combinator)=?$/
|
129
|
+
true
|
130
|
+
else
|
131
|
+
name.split(/_and_|_or_/).select {|n| !@context.attribute_method?(n)}.empty?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def build_grouping(params = {})
|
136
|
+
params ||= {}
|
137
|
+
new_grouping(params).tap do |new_grouping|
|
138
|
+
self.groupings << new_grouping
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def new_grouping(params = {})
|
143
|
+
Grouping.new(@context).build(params)
|
144
|
+
end
|
145
|
+
|
146
|
+
def build(params)
|
147
|
+
params.with_indifferent_access.each do |key, value|
|
148
|
+
case key
|
149
|
+
when /^(g|c|m)$/
|
150
|
+
self.send("#{key}=", value)
|
151
|
+
else
|
152
|
+
write_attribute(key.to_s, value)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def write_attribute(name, val)
|
161
|
+
# TODO: Methods
|
162
|
+
if condition = Condition.extract(@context, name, val)
|
163
|
+
self[name] = condition
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def read_attribute(name)
|
168
|
+
if self[name].respond_to?(:value)
|
169
|
+
self[name].value
|
170
|
+
else
|
171
|
+
self[name]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def strip_predicate_and_index(str)
|
176
|
+
string = str.split(/\(/).first
|
177
|
+
Predicate.detect_and_strip_from_string!(string)
|
178
|
+
string
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
class Node
|
4
|
+
attr_reader :context
|
5
|
+
delegate :contextualize, :to => :context
|
6
|
+
class_attribute :i18n_words
|
7
|
+
class_attribute :i18n_aliases
|
8
|
+
self.i18n_words = []
|
9
|
+
self.i18n_aliases = {}
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def i18n_word(*args)
|
13
|
+
self.i18n_words += args.map(&:to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
def i18n_alias(opts = {})
|
17
|
+
self.i18n_aliases.merge! Hash[opts.map {|k, v| [k.to_s, v.to_s]}]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(context)
|
22
|
+
@context = context
|
23
|
+
end
|
24
|
+
|
25
|
+
def translate(key, options = {})
|
26
|
+
key = i18n_aliases[key.to_s] if i18n_aliases.has_key?(key.to_s)
|
27
|
+
if i18n_words.include?(key.to_s)
|
28
|
+
Translate.word(key)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|