search_scope 0.1.9 → 0.2.1
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/Manifest +1 -0
- data/Rakefile +1 -1
- data/lib/search_scope.rb +184 -10
- data/search_scope.gemspec +5 -6
- data/search_scope.tmproj +145 -0
- metadata +7 -4
data/Manifest
CHANGED
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'rake'
|
3
3
|
require 'echoe'
|
4
4
|
|
5
|
-
Echoe.new('search_scope', '0.1
|
5
|
+
Echoe.new('search_scope', '0.2.1') do |p|
|
6
6
|
p.description = "Simplify searching a model by defining custom named_scopes."
|
7
7
|
p.project = 'search-scope'
|
8
8
|
p.url = "http://rubyforge.org/projects/search-scope"
|
data/lib/search_scope.rb
CHANGED
@@ -7,6 +7,102 @@ module SearchScope
|
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
|
+
|
11
|
+
# filter_scope :author_gender, lambda { |value| { :conditions => ["authors.gender = ?", value], :include => {:authorships => :author} } } do
|
12
|
+
# filter_option :male, :label => 'Male Authors'
|
13
|
+
# filter_option :female, :label => 'Female Authors'
|
14
|
+
# filter_option :unspecified, :scope => :unspecified_author_gender
|
15
|
+
# end
|
16
|
+
|
17
|
+
|
18
|
+
class FilterScopeOption
|
19
|
+
#options can supply a value to fill in for their filter_scope, or a separate scope (symbol for a named_scope) or conditions/include to use instead.
|
20
|
+
attr_reader :key, :value, :label, :scope
|
21
|
+
def initialize(key, value, label, scope=nil)
|
22
|
+
label ||= key.to_s.titleize
|
23
|
+
value ||= key
|
24
|
+
value = value.to_s if value.is_a? Symbol
|
25
|
+
@key, @value, @label, @scope = key, value, label, scope
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class FilterScope
|
30
|
+
attr_reader :name, :label, :filter_options, :filter_option_keys
|
31
|
+
def initialize(name, label, options={}, &block)
|
32
|
+
label ||= name.to_s.titleize
|
33
|
+
@name, @label = name, label
|
34
|
+
@filter_options = HashWithIndifferentAccess.new
|
35
|
+
@filter_option_keys = []
|
36
|
+
@omit_from_ui = true if options.delete(:omit_from_ui)
|
37
|
+
#eval the block so that filter options can be initialized. Should only have calls to filter_option
|
38
|
+
instance_eval(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def omit_from_ui?
|
42
|
+
@omit_from_ui ? true : false
|
43
|
+
end
|
44
|
+
|
45
|
+
def filter_option(name, options={})
|
46
|
+
name = name.to_s
|
47
|
+
raise "Already defined a filter_option (#{name.inspect}) for filter_scope (#{self.name})}" if filter_option_keys.include? name
|
48
|
+
@filter_options_ordered = nil
|
49
|
+
filter_option_keys << name
|
50
|
+
filter_options[name] = FilterScopeOption.new(name, options[:value], options[:label], options[:scope])
|
51
|
+
end
|
52
|
+
|
53
|
+
def filter_options_ordered
|
54
|
+
return @filter_options_ordered if @filter_options_ordered
|
55
|
+
@filter_options_ordered = []
|
56
|
+
filter_option_keys.each do |key|
|
57
|
+
@filter_options_ordered << filter_options[key]
|
58
|
+
end
|
59
|
+
@filter_options_ordered
|
60
|
+
end
|
61
|
+
|
62
|
+
def key
|
63
|
+
@key ||= "filter_#{name}".intern
|
64
|
+
end
|
65
|
+
|
66
|
+
def params_key
|
67
|
+
@params_key ||= key.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def selected_option_for(params)
|
71
|
+
selected_option_key = params[self.params_key]
|
72
|
+
self.filter_options[selected_option_key]
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
#filter scope blocks are not passed through to named_scope. Instead they are eval'd for filter_options.
|
78
|
+
def filter_scope(name, proc, options={}, &block)
|
79
|
+
proc, options = nil, proc if proc.is_a?(Hash)
|
80
|
+
#default the search to a LIKE search if nothing is given
|
81
|
+
label = options.delete(:label)
|
82
|
+
#setup standard filters
|
83
|
+
if proc
|
84
|
+
#do nothing, it's custom
|
85
|
+
elsif options.blank?
|
86
|
+
proc = lambda { |term| { :conditions => ["#{table_name}.#{name} LIKE ?", "%#{term}%"] } }
|
87
|
+
elsif options.is_a?(Hash) && options[:search_type]
|
88
|
+
case options[:search_type]
|
89
|
+
when :exact_match
|
90
|
+
proc = lambda { |term| { :conditions => ["#{table_name}.#{name} = ?", term] } }
|
91
|
+
else
|
92
|
+
raise "unknown search_type for filter_scope: (#{name} - #{options[:search_type]})"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
raise "Already defined a filter_scope with name: #{name.inspect}" if filter_scope_keys.include? name
|
96
|
+
@filter_scopes_ordered = nil
|
97
|
+
|
98
|
+
scope = FilterScope.new name, label, :omit_from_ui => options.delete(:omit_from_ui), &block
|
99
|
+
|
100
|
+
filter_scope_keys << scope.key
|
101
|
+
filter_scopes[scope.key] = scope
|
102
|
+
|
103
|
+
named_scope(scope.key, proc || options)
|
104
|
+
end
|
105
|
+
|
10
106
|
def sort_search_by(name, options={})
|
11
107
|
options[:label] ||= name.to_s.titleize
|
12
108
|
if options[:order].blank?
|
@@ -22,8 +118,6 @@ module SearchScope
|
|
22
118
|
end
|
23
119
|
options[:order], options[:reverse] = options[:reverse], options[:order] if options[:reverse_orders]
|
24
120
|
|
25
|
-
# puts "***(#{self.name}).sort_search_by :#{name}, #{options.inspect}"
|
26
|
-
|
27
121
|
raise "you must supply a Symbol for a name to new_sortable_by (#{name.inspect})" unless name.is_a? Symbol
|
28
122
|
return if sort_choices_hash.keys.include? name.to_s
|
29
123
|
#the first sort added becomes the default sorting for all searches
|
@@ -34,7 +128,7 @@ module SearchScope
|
|
34
128
|
sort_choices << sort_choices_hash[name]
|
35
129
|
end
|
36
130
|
|
37
|
-
def search_scope(name, options
|
131
|
+
def search_scope(name, options={}, &block)
|
38
132
|
#default the search to a LIKE search if nothing is given
|
39
133
|
if options.blank?
|
40
134
|
options = lambda { |term| { :conditions => ["#{table_name}.#{name} LIKE ?", "%#{term}%"] } }
|
@@ -56,7 +150,11 @@ module SearchScope
|
|
56
150
|
def search_scope_keys
|
57
151
|
@search_scope_keys ||= []
|
58
152
|
end
|
59
|
-
|
153
|
+
|
154
|
+
def filter_scope_keys
|
155
|
+
@filter_scope_keys ||= []
|
156
|
+
end
|
157
|
+
|
60
158
|
def default_sort_choice
|
61
159
|
@default_sort_choice
|
62
160
|
end
|
@@ -74,6 +172,49 @@ module SearchScope
|
|
74
172
|
@quick_search_scopes ||= []
|
75
173
|
end
|
76
174
|
|
175
|
+
def filter_scopes
|
176
|
+
@filter_scopes ||= HashWithIndifferentAccess.new
|
177
|
+
end
|
178
|
+
|
179
|
+
def filter_scopes_ordered
|
180
|
+
return @filter_scopes_ordered if @filter_scopes_ordered
|
181
|
+
@filter_scopes_ordered = []
|
182
|
+
filter_scope_keys.each do |key|
|
183
|
+
@filter_scopes_ordered << filter_scopes[key]
|
184
|
+
end
|
185
|
+
@filter_scopes_ordered
|
186
|
+
end
|
187
|
+
|
188
|
+
def active_filter_scopes_for(params, options={}, &block)
|
189
|
+
active_filter_scopes = [] unless block
|
190
|
+
filter_scopes_ordered.each do |filter_scope|
|
191
|
+
selected_option = filter_scope.selected_option_for(params)
|
192
|
+
if selected_option
|
193
|
+
if block
|
194
|
+
yield filter_scope, selected_option
|
195
|
+
else
|
196
|
+
active_filter_scopes << [filter_scope, selected_option]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
block ? nil : active_filter_scopes
|
201
|
+
end
|
202
|
+
|
203
|
+
def inactive_filter_scopes_for(params, options={}, &block)
|
204
|
+
inactive_filter_scopes = [] unless block
|
205
|
+
filter_scopes_ordered.each do |filter_scope|
|
206
|
+
selected_option = filter_scope.selected_option_for(params)
|
207
|
+
unless selected_option
|
208
|
+
if block
|
209
|
+
yield filter_scope
|
210
|
+
else
|
211
|
+
inactive_filter_scopes << filter_scope
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
block ? nil : inactive_filter_scopes
|
216
|
+
end
|
217
|
+
|
77
218
|
def sort_search_by_options(sort_by, reverse=nil)
|
78
219
|
if reverse.nil? || reverse.empty?
|
79
220
|
reverse = false
|
@@ -110,15 +251,15 @@ module SearchScope
|
|
110
251
|
end
|
111
252
|
scopes
|
112
253
|
end
|
113
|
-
|
114
|
-
def get_named_scope_from_object(object, scope, *args)
|
115
|
-
scope = object.send(scope, *args)
|
116
|
-
end
|
117
254
|
|
118
|
-
def
|
119
|
-
scope_name = "search_#{scope}".intern
|
255
|
+
def get_named_scope_from_object(object, scope_name, *args)
|
120
256
|
scope = object.send(scope_name, *args)
|
121
257
|
end
|
258
|
+
|
259
|
+
def get_search_scope_from_object(object, scope_name, *args)
|
260
|
+
scope_name = "search_#{scope_name}".intern
|
261
|
+
get_named_scope_from_object object, scope_name, *args
|
262
|
+
end
|
122
263
|
|
123
264
|
def quick_search_scope_options(quick_search_terms, split_terms)
|
124
265
|
conditions = []
|
@@ -151,11 +292,26 @@ module SearchScope
|
|
151
292
|
{:conditions => conditions_sql, :include => includes}
|
152
293
|
end
|
153
294
|
|
295
|
+
def extract_filter_params(params)
|
296
|
+
filter_params = {}
|
297
|
+
params.keys.each do |key|
|
298
|
+
key = key.to_s#.intern
|
299
|
+
filter_params[key] = params.delete key if filter_scope_keys.include? key.intern
|
300
|
+
end
|
301
|
+
filter_params
|
302
|
+
end
|
303
|
+
|
154
304
|
#this searches by chaining all of the named_scopes (search_scopes) that were included in the params
|
155
305
|
def search(params={})
|
306
|
+
params = params.clone
|
307
|
+
#this lets you do a quick_search like: Book.search('foo') instead of Book.search(:quick_search => 'foo')
|
308
|
+
params = {:quick_search => params} unless params.kind_of? Hash
|
156
309
|
params.reverse_merge! :split_terms => true
|
157
310
|
paginate = params.delete :paginate
|
158
311
|
aggregate_scope = self
|
312
|
+
|
313
|
+
filter_params = extract_filter_params params
|
314
|
+
|
159
315
|
search_scopes(params).each do |scope|
|
160
316
|
if scope.is_a? Symbol
|
161
317
|
# aggregate_scope = aggregate_scope.send(scope)
|
@@ -167,6 +323,24 @@ module SearchScope
|
|
167
323
|
raise "unsupported type for search scope: #{scope.inspect}"
|
168
324
|
end
|
169
325
|
end
|
326
|
+
|
327
|
+
filter_params.each do |filter_scope_key, filter_option_key|
|
328
|
+
filter_scope_key = filter_scope_key.intern unless filter_scope_key.is_a? Symbol
|
329
|
+
filter_option_key = filter_option_key.intern unless filter_option_key.blank? || filter_option_key.is_a?(Symbol)
|
330
|
+
next if filter_option_key.blank?
|
331
|
+
|
332
|
+
filter_scope = filter_scopes[filter_scope_key]
|
333
|
+
filter_option = filter_scope.filter_options[filter_option_key]
|
334
|
+
|
335
|
+
raise "search_scope: No filter option found with key: #{filter_option_key.inspect}" unless filter_option
|
336
|
+
|
337
|
+
if filter_option.scope
|
338
|
+
aggregate_scope = get_named_scope_from_object(aggregate_scope, filter_option.scope)
|
339
|
+
else
|
340
|
+
aggregate_scope = get_named_scope_from_object(aggregate_scope, filter_scope_key, filter_option.value)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
170
344
|
#TODO add filter_scopes here as well
|
171
345
|
aggregate_scope = aggregate_named_scope(aggregate_scope, params)
|
172
346
|
|
data/search_scope.gemspec
CHANGED
@@ -2,26 +2,25 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{search_scope}
|
5
|
-
s.version = "0.1
|
5
|
+
s.version = "0.2.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Ryan Owens"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-07-24}
|
10
10
|
s.description = %q{Simplify searching a model by defining custom named_scopes.}
|
11
11
|
s.email = %q{ryan@infoether.com}
|
12
12
|
s.extra_rdoc_files = ["CHANGELOG", "lib/search_scope.rb", "README"]
|
13
|
-
s.files = ["CHANGELOG", "init.rb", "lib/search_scope.rb", "Manifest", "Rakefile", "README", "search_scope.gemspec"]
|
14
|
-
s.has_rdoc = true
|
13
|
+
s.files = ["CHANGELOG", "init.rb", "lib/search_scope.rb", "Manifest", "Rakefile", "README", "search_scope.tmproj", "search_scope.gemspec"]
|
15
14
|
s.homepage = %q{http://rubyforge.org/projects/search-scope}
|
16
15
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Search_scope", "--main", "README"]
|
17
16
|
s.require_paths = ["lib"]
|
18
17
|
s.rubyforge_project = %q{search-scope}
|
19
|
-
s.rubygems_version = %q{1.3.
|
18
|
+
s.rubygems_version = %q{1.3.3}
|
20
19
|
s.summary = %q{Simplify searching a model by defining custom named_scopes.}
|
21
20
|
|
22
21
|
if s.respond_to? :specification_version then
|
23
22
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
-
s.specification_version =
|
23
|
+
s.specification_version = 3
|
25
24
|
|
26
25
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
26
|
else
|
data/search_scope.tmproj
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>currentDocument</key>
|
6
|
+
<string>search_scope_notes.txt</string>
|
7
|
+
<key>documents</key>
|
8
|
+
<array>
|
9
|
+
<dict>
|
10
|
+
<key>expanded</key>
|
11
|
+
<true/>
|
12
|
+
<key>name</key>
|
13
|
+
<string>search_scope</string>
|
14
|
+
<key>regexFolderFilter</key>
|
15
|
+
<string>!.*/(\coverage|\.svn|\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
|
16
|
+
<key>sourceDirectory</key>
|
17
|
+
<string></string>
|
18
|
+
</dict>
|
19
|
+
</array>
|
20
|
+
<key>fileHierarchyDrawerWidth</key>
|
21
|
+
<integer>370</integer>
|
22
|
+
<key>metaData</key>
|
23
|
+
<dict>
|
24
|
+
<key>README</key>
|
25
|
+
<dict>
|
26
|
+
<key>caret</key>
|
27
|
+
<dict>
|
28
|
+
<key>column</key>
|
29
|
+
<integer>0</integer>
|
30
|
+
<key>line</key>
|
31
|
+
<integer>0</integer>
|
32
|
+
</dict>
|
33
|
+
<key>firstVisibleColumn</key>
|
34
|
+
<integer>0</integer>
|
35
|
+
<key>firstVisibleLine</key>
|
36
|
+
<integer>0</integer>
|
37
|
+
</dict>
|
38
|
+
<key>Rakefile</key>
|
39
|
+
<dict>
|
40
|
+
<key>caret</key>
|
41
|
+
<dict>
|
42
|
+
<key>column</key>
|
43
|
+
<integer>0</integer>
|
44
|
+
<key>line</key>
|
45
|
+
<integer>15</integer>
|
46
|
+
</dict>
|
47
|
+
<key>columnSelection</key>
|
48
|
+
<false/>
|
49
|
+
<key>firstVisibleColumn</key>
|
50
|
+
<integer>0</integer>
|
51
|
+
<key>firstVisibleLine</key>
|
52
|
+
<integer>0</integer>
|
53
|
+
<key>selectFrom</key>
|
54
|
+
<dict>
|
55
|
+
<key>column</key>
|
56
|
+
<integer>0</integer>
|
57
|
+
<key>line</key>
|
58
|
+
<integer>0</integer>
|
59
|
+
</dict>
|
60
|
+
<key>selectTo</key>
|
61
|
+
<dict>
|
62
|
+
<key>column</key>
|
63
|
+
<integer>0</integer>
|
64
|
+
<key>line</key>
|
65
|
+
<integer>15</integer>
|
66
|
+
</dict>
|
67
|
+
</dict>
|
68
|
+
<key>lib/search_scope.rb</key>
|
69
|
+
<dict>
|
70
|
+
<key>caret</key>
|
71
|
+
<dict>
|
72
|
+
<key>column</key>
|
73
|
+
<integer>0</integer>
|
74
|
+
<key>line</key>
|
75
|
+
<integer>378</integer>
|
76
|
+
</dict>
|
77
|
+
<key>columnSelection</key>
|
78
|
+
<false/>
|
79
|
+
<key>firstVisibleColumn</key>
|
80
|
+
<integer>0</integer>
|
81
|
+
<key>firstVisibleLine</key>
|
82
|
+
<integer>211</integer>
|
83
|
+
<key>selectFrom</key>
|
84
|
+
<dict>
|
85
|
+
<key>column</key>
|
86
|
+
<integer>0</integer>
|
87
|
+
<key>line</key>
|
88
|
+
<integer>384</integer>
|
89
|
+
</dict>
|
90
|
+
<key>selectTo</key>
|
91
|
+
<dict>
|
92
|
+
<key>column</key>
|
93
|
+
<integer>0</integer>
|
94
|
+
<key>line</key>
|
95
|
+
<integer>378</integer>
|
96
|
+
</dict>
|
97
|
+
</dict>
|
98
|
+
<key>search_scope_notes.txt</key>
|
99
|
+
<dict>
|
100
|
+
<key>caret</key>
|
101
|
+
<dict>
|
102
|
+
<key>column</key>
|
103
|
+
<integer>0</integer>
|
104
|
+
<key>line</key>
|
105
|
+
<integer>47</integer>
|
106
|
+
</dict>
|
107
|
+
<key>firstVisibleColumn</key>
|
108
|
+
<integer>0</integer>
|
109
|
+
<key>firstVisibleLine</key>
|
110
|
+
<integer>36</integer>
|
111
|
+
</dict>
|
112
|
+
</dict>
|
113
|
+
<key>openDocuments</key>
|
114
|
+
<array>
|
115
|
+
<string>lib/search_scope.rb</string>
|
116
|
+
<string>search_scope_notes.txt</string>
|
117
|
+
<string>README</string>
|
118
|
+
<string>Rakefile</string>
|
119
|
+
</array>
|
120
|
+
<key>showFileHierarchyDrawer</key>
|
121
|
+
<false/>
|
122
|
+
<key>showFileHierarchyPanel</key>
|
123
|
+
<true/>
|
124
|
+
<key>treeState</key>
|
125
|
+
<dict>
|
126
|
+
<key>search_scope</key>
|
127
|
+
<dict>
|
128
|
+
<key>isExpanded</key>
|
129
|
+
<true/>
|
130
|
+
<key>subItems</key>
|
131
|
+
<dict>
|
132
|
+
<key>lib</key>
|
133
|
+
<dict>
|
134
|
+
<key>isExpanded</key>
|
135
|
+
<true/>
|
136
|
+
<key>subItems</key>
|
137
|
+
<dict/>
|
138
|
+
</dict>
|
139
|
+
</dict>
|
140
|
+
</dict>
|
141
|
+
</dict>
|
142
|
+
<key>windowFrame</key>
|
143
|
+
<string>{{-1920, -58}, {1920, 1200}}</string>
|
144
|
+
</dict>
|
145
|
+
</plist>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: search_scope
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Owens
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-07-24 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -30,9 +30,12 @@ files:
|
|
30
30
|
- Manifest
|
31
31
|
- Rakefile
|
32
32
|
- README
|
33
|
+
- search_scope.tmproj
|
33
34
|
- search_scope.gemspec
|
34
35
|
has_rdoc: true
|
35
36
|
homepage: http://rubyforge.org/projects/search-scope
|
37
|
+
licenses: []
|
38
|
+
|
36
39
|
post_install_message:
|
37
40
|
rdoc_options:
|
38
41
|
- --line-numbers
|
@@ -58,9 +61,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
61
|
requirements: []
|
59
62
|
|
60
63
|
rubyforge_project: search-scope
|
61
|
-
rubygems_version: 1.3.
|
64
|
+
rubygems_version: 1.3.3
|
62
65
|
signing_key:
|
63
|
-
specification_version:
|
66
|
+
specification_version: 3
|
64
67
|
summary: Simplify searching a model by defining custom named_scopes.
|
65
68
|
test_files: []
|
66
69
|
|