search_me 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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: []