search_me 0.0.3
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/LICENSE +20 -0
- data/lib/search_me/filters.rb +100 -0
- data/lib/search_me/search.rb +272 -0
- data/lib/search_me.rb +25 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4dda6eb047e473b4ddb6589405b67e8053f35f6b
|
4
|
+
data.tar.gz: 6f8b867f9554d4fdd6c8d84c86543196ebb617fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4f2223995371daf6b83e0aaee00ac97501cc877c473552fdb3debb9e2c004989a5b5cd51cb12336dd09b8eb13e179998b8d7bf784c70dc1c6c6d7734bd99953f
|
7
|
+
data.tar.gz: 88d8b938bca88d7c96d959f2c364f5aa64b181064aac55f73803522b4b5d5bca456f5c0f18c9d9d1643d27aefdb65e93cb2d14563deae3148094f2cf3e4409f5
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 John Hager
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module SearchMe
|
2
|
+
module Filters
|
3
|
+
def filter_month_quarter_or_year(month=nil, quarter=nil, year=nil)
|
4
|
+
month, quarter, year = sanitize_params(month, quarter, year)
|
5
|
+
if month
|
6
|
+
filter_month(month, year)
|
7
|
+
elsif quarter
|
8
|
+
filter_quarter(quarter, year)
|
9
|
+
else
|
10
|
+
filter_year(year)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def filter_year(year = nil)
|
15
|
+
year = sanitize_params(year).first
|
16
|
+
|
17
|
+
year ||= Date.today.year
|
18
|
+
date = Date.new(year, 1, 1)
|
19
|
+
|
20
|
+
build_between_query_for(year_for_date(date))
|
21
|
+
end
|
22
|
+
|
23
|
+
def filter_month(month = nil, year = nil)
|
24
|
+
month, year = sanitize_params(month, year)
|
25
|
+
|
26
|
+
month ||= Date.today.month
|
27
|
+
year ||= Date.today.year
|
28
|
+
date = Date.new(year, month, 1)
|
29
|
+
|
30
|
+
build_between_query_for(month_for_date(date))
|
31
|
+
end
|
32
|
+
|
33
|
+
def filter_quarter(quarter = nil, year = nil)
|
34
|
+
quarter, year = sanitize_params(quarter, year)
|
35
|
+
quarter ||= (Date.today.month - 1) / 3 + 1
|
36
|
+
return self if quarter > 4
|
37
|
+
|
38
|
+
year ||= Date.today.year
|
39
|
+
month = (quarter - 1) * 3 + 1
|
40
|
+
date = Date.new(year, month, 1)
|
41
|
+
|
42
|
+
build_between_query_for(quarter_for_date(date))
|
43
|
+
end
|
44
|
+
|
45
|
+
def filter_named_duration(duration)
|
46
|
+
today = Date.today
|
47
|
+
|
48
|
+
duration = case duration
|
49
|
+
when "current_month"
|
50
|
+
month_for_date(today)
|
51
|
+
when "last_month"
|
52
|
+
month_for_date(1.month.ago)
|
53
|
+
when "f_last_month"
|
54
|
+
month_for_date(2.month.ago)
|
55
|
+
when "last_quarter"
|
56
|
+
quarter_for_date(today.beginning_of_quarter - 1)
|
57
|
+
when "current_year"
|
58
|
+
year_for_date(today)
|
59
|
+
when "last_year"
|
60
|
+
year_for_date(1.year.ago)
|
61
|
+
when "f_last_year"
|
62
|
+
year_for_date(2.year.ago)
|
63
|
+
else
|
64
|
+
nil # do nothing
|
65
|
+
end
|
66
|
+
if duration
|
67
|
+
build_between_query_for(duration)
|
68
|
+
else
|
69
|
+
self.all
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def build_between_query_for(duration)
|
75
|
+
field = SearchMe.config.time_field
|
76
|
+
in_created_at = "(#{field} > ? AND #{field} < ?)"
|
77
|
+
on_created_at = "(#{field} = ?) OR (#{field} = ?)"
|
78
|
+
self.where(
|
79
|
+
"#{in_created_at} OR #{on_created_at}", *duration, *duration
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
def month_for_date(date)
|
84
|
+
[date.beginning_of_month, date.end_of_month]
|
85
|
+
end
|
86
|
+
|
87
|
+
def year_for_date(date)
|
88
|
+
[date.beginning_of_year, date.end_of_year]
|
89
|
+
end
|
90
|
+
|
91
|
+
def quarter_for_date(date)
|
92
|
+
[date.beginning_of_quarter, date.end_of_quarter]
|
93
|
+
end
|
94
|
+
|
95
|
+
def sanitize_params(*params)
|
96
|
+
# i.e. Integer or nil. Also no zero year, month, day
|
97
|
+
params.map { |p| p.to_i if p.present? && !p.to_i.zero? }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
module SearchMe
|
2
|
+
module Search
|
3
|
+
def search_attributes
|
4
|
+
@search_attributes ||= default_hash
|
5
|
+
end
|
6
|
+
|
7
|
+
def advanced_search_blocks
|
8
|
+
@advanced_search_blocks ||= default_hash(:never_a_key_like_this)
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_hash(flat_key = :simple)
|
12
|
+
Hash.new { |h,k|
|
13
|
+
h[k] = (k == flat_key ? [] : Hash.new { |nh,nk| nh[nk] = [] })
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def attr_search(*attributes, type: :simple)
|
18
|
+
unless ([:simple] + self.reflections.keys).include?(type)
|
19
|
+
raise ArgumentError, 'incorect type given'
|
20
|
+
end
|
21
|
+
|
22
|
+
search_attributes_hash!(attributes, type, search_attributes)
|
23
|
+
end
|
24
|
+
|
25
|
+
def alias_advanced_search(attribute, type: :simple, &block)
|
26
|
+
advanced_search_blocks[type][attribute] = block
|
27
|
+
end
|
28
|
+
|
29
|
+
def search_attributes_hash!(attributes, type, hash = default_hash)
|
30
|
+
if type == :simple
|
31
|
+
hash[:simple] += attributes
|
32
|
+
hash[:simple] = hash[:simple].uniq
|
33
|
+
else
|
34
|
+
reflection = self.reflections[type]
|
35
|
+
macro = reflection.macro
|
36
|
+
klass = klass_for_reflection(reflection)
|
37
|
+
|
38
|
+
if macro == :has_many
|
39
|
+
macro = :has_many_through if reflection.options[:through]
|
40
|
+
end
|
41
|
+
|
42
|
+
hash[macro][type] += attributes
|
43
|
+
hash[macro][type] = hash[macro][type].uniq
|
44
|
+
|
45
|
+
unless klass.kind_of?(SearchMe::Search)
|
46
|
+
klass.extend(SearchMe::Search)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
hash = sanitize_params!(hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
def search_me(attribute, term)
|
53
|
+
self.where(simple_search_where_condition(attribute, term))
|
54
|
+
end
|
55
|
+
|
56
|
+
def join(array)
|
57
|
+
array.delete_if { |condition| condition.blank? }.join(joiner)
|
58
|
+
end
|
59
|
+
|
60
|
+
def search(term)
|
61
|
+
@joiner = :or
|
62
|
+
condition = join([
|
63
|
+
simple_search_condition(term),
|
64
|
+
reflection_search_condition(term)
|
65
|
+
])
|
66
|
+
self.where(condition)
|
67
|
+
end
|
68
|
+
|
69
|
+
def joiner
|
70
|
+
@joiner ||= :or
|
71
|
+
" #{@joiner.upcase} "
|
72
|
+
end
|
73
|
+
|
74
|
+
def advanced_search(search_terms)
|
75
|
+
search_terms = sanitize_params!(search_terms)
|
76
|
+
@joiner = :and
|
77
|
+
|
78
|
+
hash = default_hash
|
79
|
+
search_terms.each do |type, attributes|
|
80
|
+
search_attributes_hash!(attributes.keys,type,hash)
|
81
|
+
end
|
82
|
+
@this_search_attributes = hash
|
83
|
+
|
84
|
+
conditions = @this_search_attributes.keys.map { |type|
|
85
|
+
case type
|
86
|
+
when :simple
|
87
|
+
join(@this_search_attributes[type].map { |attribute|
|
88
|
+
term = search_terms[type][attribute]
|
89
|
+
if advanced_search_block_for?(type, attribute)
|
90
|
+
call_advanced_search_block_for(type, attribute, term)
|
91
|
+
else
|
92
|
+
simple_search_where_condition(attribute, term)
|
93
|
+
end
|
94
|
+
})
|
95
|
+
when :belongs_to
|
96
|
+
self.advanced_search_reflection_group(type,search_terms) {
|
97
|
+
|reflection,objs|
|
98
|
+
"#{reflection.name}_id IN (#{object_ids(objs).join(',')})"
|
99
|
+
}
|
100
|
+
when :has_one
|
101
|
+
self.advanced_search_reflection_group(type,search_terms) {
|
102
|
+
|reflection,objs|
|
103
|
+
f_key = "#{name_for(reflection.active_record.name)}_id"
|
104
|
+
|
105
|
+
"id IN (#{object_ids(objs, f_key).join(',')})"
|
106
|
+
}
|
107
|
+
when :has_many
|
108
|
+
self.advanced_search_reflection_group(type,search_terms) {
|
109
|
+
|reflection,objs|
|
110
|
+
f_key = reflection.options
|
111
|
+
.fetch(:foreign_key) { "#{self.to_s.underscore}_id" }
|
112
|
+
|
113
|
+
"id IN (#{object_ids(objs, f_key).join(',')})"
|
114
|
+
}
|
115
|
+
when :has_many_through
|
116
|
+
warn 'WARNING: has_many_through relationships not available'
|
117
|
+
end
|
118
|
+
}
|
119
|
+
self.where(join(conditions))
|
120
|
+
end
|
121
|
+
|
122
|
+
def reflection_search_condition(term)
|
123
|
+
macro_groups = search_attributes
|
124
|
+
@this_search_attributes = macro_groups
|
125
|
+
|
126
|
+
condition = macro_groups.keys.map { |type|
|
127
|
+
case type
|
128
|
+
when :belongs_to
|
129
|
+
self.search_reflection_group(type, term) { |reflection, objs|
|
130
|
+
"#{reflection.name}_id IN (#{object_ids(objs).join(',')})"
|
131
|
+
}
|
132
|
+
when :has_many
|
133
|
+
self.search_reflection_group(type, term) { |reflection, objs|
|
134
|
+
f_key = reflection.options
|
135
|
+
.fetch(:foreign_key) { "#{self.to_s.underscore}_id" }
|
136
|
+
|
137
|
+
"id IN (#{object_ids(objs, f_key).join(',')})"
|
138
|
+
}
|
139
|
+
when :has_one
|
140
|
+
self.search_reflection_group(type, term) { |reflection, objs|
|
141
|
+
f_key = "#{name_for(reflection.active_record.name)}_id"
|
142
|
+
|
143
|
+
"id IN (#{object_ids(objs, f_key).join(',')})"
|
144
|
+
}
|
145
|
+
when :has_many_through
|
146
|
+
warn 'WARNING: has_many_through relationships not available'
|
147
|
+
end
|
148
|
+
}
|
149
|
+
join(condition)
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
def reflection_search(term)
|
154
|
+
self.where(reflection_search_condition(term))
|
155
|
+
end
|
156
|
+
|
157
|
+
def map_reflection_group(type, outer_block)
|
158
|
+
cond = @this_search_attributes[type].map {|reflection, attributes|
|
159
|
+
reflection = self.reflections[reflection]
|
160
|
+
klass = klass_for_reflection(reflection)
|
161
|
+
|
162
|
+
reflection_condition = join(yield(attributes,klass,reflection))
|
163
|
+
|
164
|
+
search_result = klass.where(reflection_condition)
|
165
|
+
|
166
|
+
outer_block.call(reflection, search_result)
|
167
|
+
}
|
168
|
+
join(cond)
|
169
|
+
end
|
170
|
+
|
171
|
+
def search_reflection_group(type, term, &block)
|
172
|
+
map_reflection_group(type, block) do |attributes,klass,reflection|
|
173
|
+
attributes.map { |attribute|
|
174
|
+
name = reflection.name
|
175
|
+
if advanced_search_block_for?(name, attribute)
|
176
|
+
call_advanced_search_block_for(name, attribute, term)
|
177
|
+
else
|
178
|
+
klass.simple_search_where_condition(attribute, term)
|
179
|
+
end
|
180
|
+
}
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def advanced_search_reflection_group(type, search_terms, &block)
|
185
|
+
map_reflection_group(type, block) do |attributes,klass,reflection|
|
186
|
+
attributes.map { |attribute|
|
187
|
+
name = reflection.name
|
188
|
+
term = search_terms[name][attribute]
|
189
|
+
if advanced_search_block_for?(name, attribute)
|
190
|
+
call_advanced_search_block_for(name, attribute, term)
|
191
|
+
else
|
192
|
+
klass.simple_search_where_condition(attribute, term)
|
193
|
+
end
|
194
|
+
}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def simple_search_condition(term)
|
199
|
+
condition = search_attributes[:simple].map { |attribute|
|
200
|
+
simple_search_where_condition(attribute, term)
|
201
|
+
}
|
202
|
+
join(condition)
|
203
|
+
end
|
204
|
+
|
205
|
+
def simple_search(term)
|
206
|
+
self.where(simple_search_condition(term))
|
207
|
+
end
|
208
|
+
|
209
|
+
def simple_search_where_condition(attribute, term)
|
210
|
+
table_column = "#{self.table_name}.#{attribute}"
|
211
|
+
column = self.columns.find { |col| col.name == attribute.to_s }
|
212
|
+
|
213
|
+
case column.type
|
214
|
+
when :string, :integer, :text, :float, :decimal
|
215
|
+
"CAST(#{table_column} AS CHAR) LIKE '%#{term}%'"
|
216
|
+
when :boolean
|
217
|
+
term = {
|
218
|
+
true => "= 't'", false => "= 'f'", nil => 'IS NULL',
|
219
|
+
1 => "= 't'", 0 => "= 'f'", '1' => "= 't'", '0' => "= 'f'"
|
220
|
+
}.fetch(term) {
|
221
|
+
good_args = term.keys.map(:inspect).join(',')
|
222
|
+
error = "boolean column term must be #{good_args}"
|
223
|
+
raise ArgumentError, error
|
224
|
+
}
|
225
|
+
"#{table_column} #{term}"
|
226
|
+
else
|
227
|
+
warn "#{column.type} type is not supported by SearchMe::Search"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def advanced_search_block_for?(type, attribute)
|
232
|
+
!advanced_search_blocks[type].blank? &&
|
233
|
+
!advanced_search_blocks[type][attribute].blank?
|
234
|
+
end
|
235
|
+
|
236
|
+
def call_advanced_search_block_for(type, attribute, term)
|
237
|
+
advanced_search_blocks[type][attribute].call(term)
|
238
|
+
end
|
239
|
+
|
240
|
+
def object_ids(objects, column = nil)
|
241
|
+
(column ? objects.map(&column.to_sym) : objects.ids) << -5318008
|
242
|
+
end
|
243
|
+
|
244
|
+
def klass_for_reflection(reflection)
|
245
|
+
if name = reflection.options[:class_name]
|
246
|
+
constant_for(name)
|
247
|
+
else
|
248
|
+
constant_for(reflection.name)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def name_for(constant, plural: false)
|
253
|
+
name = constant.to_s.underscore
|
254
|
+
name = name.singularize unless plural
|
255
|
+
name
|
256
|
+
end
|
257
|
+
|
258
|
+
def constant_for(name)
|
259
|
+
name.to_s.camelize.singularize.constantize
|
260
|
+
end
|
261
|
+
|
262
|
+
def sanitize_params!(params)
|
263
|
+
params.to_hash.symbolize_keys!.each { |k,v|
|
264
|
+
if v.is_a?(Hash)
|
265
|
+
params[k] = v = v.to_hash
|
266
|
+
sanitize_params!(v) and v.symbolize_keys!
|
267
|
+
end
|
268
|
+
params.delete(k) if v.blank? && !(v == false)
|
269
|
+
}
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
data/lib/search_me.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'search_me/search'
|
2
|
+
require_relative 'search_me/filters'
|
3
|
+
|
4
|
+
module SearchMe
|
5
|
+
|
6
|
+
class Config
|
7
|
+
attr_accessor :time_field
|
8
|
+
def initialize
|
9
|
+
# Set Defaults
|
10
|
+
@time_field = :created_at
|
11
|
+
|
12
|
+
yield(self) if block_given?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
extend self
|
17
|
+
|
18
|
+
def self.config(&block)
|
19
|
+
if block_given?
|
20
|
+
@config = Config.new(&block)
|
21
|
+
else
|
22
|
+
@config ||= Config.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: search_me
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jphager2
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-27 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Uses LIKE to search attributes and return any objects of the model for
|
14
|
+
which a match is found
|
15
|
+
email: jphager2@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- LICENSE
|
21
|
+
- lib/search_me.rb
|
22
|
+
- lib/search_me/filters.rb
|
23
|
+
- lib/search_me/search.rb
|
24
|
+
homepage: https://github.com/jphager2/search_me
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubyforge_project:
|
44
|
+
rubygems_version: 2.2.2
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Allows you to define attributes of active record model and its related models
|
48
|
+
which will be searched
|
49
|
+
test_files: []
|