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 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: []