yep_searchable 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,163 @@
1
+ #YepSearchable
2
+ ==============
3
+
4
+ This is a ruby gem to search in Ruby models as ActiveRecord::Base and ActiveResources::Base.
5
+
6
+ <b>Beta notes:</b> The search is temporarily working as "contains" clauses.
7
+
8
+
9
+ ##Installing
10
+
11
+ In your Gemfile, add the following line of code:
12
+ <pre>gem 'yep_searchable'</pre>
13
+
14
+
15
+ ##Configuring
16
+
17
+ ### ActiveRecord Search
18
+ In your ActiveRecord model, add this line of code:
19
+ <pre>searchable_resource searchable_columns: [:name, :notes]</pre>
20
+
21
+ For example:
22
+ ```ruby
23
+ class Brand < ActiveRecord::Base
24
+
25
+ searchable_resource searchable_columns: [:name]
26
+ ....
27
+
28
+ end
29
+ ```
30
+
31
+ ```ruby
32
+ class Car < ActiveRecord::Base
33
+
34
+ searchable_resource searchable_columns: [:model, :color]
35
+
36
+ belongs_to :brand
37
+ ....
38
+
39
+ end
40
+ ```
41
+
42
+ ### ActiveResource Search
43
+ Your ActiveResource must implement YepSearchable::YepResource, like this:
44
+
45
+ ```ruby
46
+ class Brand < YepSearchable::YepResource
47
+ self.site = "http://127.0.0.1"
48
+ ...
49
+ end
50
+ ```
51
+
52
+ In YepResource you have two new attributes:
53
+ ```ruby
54
+ ...
55
+ self.search_path = "search_resource" # this is the default value
56
+ self.search_ids_path = "search_ids" # this is the default value
57
+ ...
58
+ ```
59
+
60
+ For each of this path you <b>must create an route and an action on source controller</b>.
61
+ Example:
62
+
63
+
64
+ ```ruby
65
+ class BrandController < ApplicationController
66
+ ...
67
+ # GET /brands/search_resource
68
+ def search_resource
69
+ @brands = Brand.search(params[:query])
70
+
71
+ render json: @brands
72
+ end
73
+
74
+ # GET /brands/search_ids
75
+ def search_ids
76
+ ids = Brand.search_only_ids(params[:query])
77
+
78
+ render json: ids
79
+ end
80
+ ...
81
+ end
82
+ ```
83
+
84
+ ### Declaring relations between ActiveRecord and YepResource
85
+ Once you search for "Ferrari" you are expect to receive the cars branded by Ferrari.
86
+ So in your ActiveRecord "searchable_resource" declaration, you should add the following option:
87
+ ```ruby
88
+ searchable_resource searchable_columns: [:name, :notes], yep_resources: [{class_name: :brand, foreign_key: :brand_id}]
89
+ ```
90
+
91
+ In this case, if you search like this:
92
+ ```ruby
93
+ Car.search("ferrari")
94
+ ```
95
+
96
+ The result will be all cars that contain "Ferrari" in the <b>model</b>, <b>color</b> or the <b>brand_id</b> equal for the brands that contains "ferrari" in the name.
97
+ Comparing to an SQL command, is something like:
98
+ ```sql
99
+ SELECT * FROM cars WHERE cars.model LIKE '%FERRARI%' OR cars.color LIKE '%FERRARI%'
100
+ --here is the trick
101
+ OR (cars.brand_id IN (SELECT id FROM brands WHERE brands.name LIKE '%FERRARI%'));
102
+ ```
103
+
104
+ ##Usage
105
+ The yep_searchable gem provide differents way for search.
106
+ You can give more than one parameter or not propagate the search.
107
+ The examples will describe the gem methods.
108
+
109
+ ### Searching
110
+ - You can search with more than one parameter:
111
+
112
+ ```ruby
113
+ Car.search("ferrari", "red")
114
+ # this will look for all cars that contain "ferrari" or "red" in the name, color or brand name
115
+ ```
116
+
117
+ - You are able to not propagate the search, the search will not look for the relations:
118
+
119
+ ```ruby
120
+ Car.search(false, "red")
121
+ # this will look for all cars that contain "red" in the name or color BUT WILL NOT SEARCH FOR BRANDS THAT CONTAINS "RED" IN THE NAME
122
+ ```
123
+
124
+ ### Searching only IDS
125
+ - You can search only the model ids using the method "search_only_ids":
126
+
127
+ ```ruby
128
+ Car.search_only_ids("ferrari", "red")
129
+ # this will look for all cars ids that contain "ferrari" or "red" in the name, color or brand name
130
+ ```
131
+
132
+ - As the normal search, you can propagate the search or not:
133
+
134
+ ```ruby
135
+ Car.search_only_ids(false, "red")
136
+ # this will look for all cars ids that contain "red" in the name or color BUT WILL NOT SEARCH FOR BRANDS THAT CONTAINS "RED" IN THE NAME
137
+ ```
138
+
139
+ ### Pagination
140
+ - You can paginate your search using the method "search_by_page", the search by page method have the following parameters:
141
+ - <i><b>only_ids</b> - default false</i>
142
+ - <i><b>page_number</b> - default nil</i>
143
+ - <i><b>number_of_rows</b> - default nil - number of rows per page</i>
144
+ - <i><b>propagate</b> - default true</i>
145
+
146
+ Example:
147
+
148
+ ```ruby
149
+ Car.search_by_page(false, 1, 30, true, "ferrari", "red")
150
+ # this will look for the first page, 30 cars
151
+ # that contain "ferrari" or "red" in the name, color or brand name
152
+ ```
153
+
154
+ ### Is searchable resource?
155
+ - The YepSearchable gem provides the capability to check if a Model is searchable by the method "is_searchable_resource?"
156
+ Example:
157
+
158
+ ```ruby
159
+ Car.is_searchable_resource?
160
+ # => true
161
+ Motorcycle.is_searchable_resource?
162
+ # => false
163
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env rake
@@ -0,0 +1,25 @@
1
+ module YepSearchable
2
+ class Configuration
3
+ attr_accessor :configs
4
+
5
+ def initialize
6
+ @@configs ||= Hash.new
7
+ end
8
+
9
+ def include_option(class_name, option_name, value)
10
+ @@configs ||= Hash.new
11
+ @@configs[class_name] ||= Hash.new
12
+ @@configs[class_name][option_name] = value
13
+ end
14
+
15
+ def retrieve_option(class_name, option_name)
16
+ @@configs ||= Hash.new
17
+
18
+ if @@configs and @@configs[class_name]
19
+ @@configs[class_name][option_name]
20
+ else
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module YepSearchable
2
+ class SearchableError < StandardError
3
+
4
+ end
5
+ end
@@ -0,0 +1,259 @@
1
+ require 'yep_searchable/searchable_error'
2
+
3
+ module YepSearchable
4
+ module SearchableResource
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def searchable_resource(options = {})
9
+ if not options[:searchable_columns]
10
+ raise SearchableError, "Searchable columns must be initialized on searchable_resource declaration"
11
+ end
12
+
13
+ attr_accessor :searchable_columns
14
+ YepSearchable.configuration.include_option(self.to_s, "searchable_columns", options[:searchable_columns])
15
+
16
+ if options[:yep_resources]
17
+ options[:yep_resources].each do |resource|
18
+ if resource[:class_name].nil? or resource[:foreign_key].nil?
19
+ raise SearchableError, "class_name and foreign_key must be declared"
20
+ end
21
+
22
+ if !resource[:class_name].to_s.camelize.constantize.superclass.to_s.eql?"YepSearchable::YepResource"
23
+ raise SearchableError, "Only classes that implement YepResource are allowed"
24
+ end
25
+ end
26
+ YepSearchable.configuration.include_option(self.to_s, "yep_resources", options[:yep_resources])
27
+ end
28
+ end
29
+
30
+ def is_searchable_resource?
31
+ if YepSearchable.configuration.nil?
32
+ return false
33
+ end
34
+ searchable_columns = YepSearchable.configuration.retrieve_option(self.to_s, 'searchable_columns')
35
+ if searchable_columns.nil? or searchable_columns.empty?
36
+ return false
37
+ else
38
+ return true
39
+ end
40
+ end
41
+
42
+ def search(propagate, *query_params)
43
+ if !propagate.to_s.eql?"true" and !propagate.to_s.eql?"false"
44
+ if query_params.nil? or query_params.empty?
45
+ query_params = []
46
+ end
47
+
48
+ while query_params.first.instance_of?(Array)
49
+ query_params = query_params.first
50
+ end
51
+ query_params << propagate
52
+
53
+ propagate = false
54
+ end
55
+
56
+ return search_by_page(false, nil, nil, propagate, query_params)
57
+ end
58
+
59
+ def search_only_ids(propagate, *query_params)
60
+ if !propagate.to_s.eql?"true" and !propagate.to_s.eql?"false"
61
+ if query_params.nil? or query_params.empty?
62
+ query_params = []
63
+ end
64
+
65
+ while query_params.first.instance_of?(Array)
66
+ query_params = query_params.first
67
+ end
68
+ query_params << propagate
69
+
70
+ propagate = false
71
+ end
72
+
73
+ return search_by_page(true, nil, nil, propagate, query_params)
74
+ end
75
+
76
+ def search_by_page(only_ids = false, page = nil, number_of_rows = nil, propagate = true, *query_param)
77
+ while query_param.first.instance_of?(Array)
78
+ query_param = query_param.first
79
+ end
80
+
81
+ if query_param.size == 1 and query_param.first.include?("$$$")
82
+ query_param = query_param.first.split("$$$")
83
+ end
84
+
85
+ if query_param.nil? or query_param.empty?
86
+ raise SearchableError, "Arguments not declared in search method"
87
+ end
88
+
89
+ if page
90
+ is_valid = Integer(page.to_s) rescue false
91
+ if !is_valid and is_valid <= 0
92
+ raise SearchableError, "Argument page must be a valid number"
93
+ end
94
+ end
95
+
96
+ if number_of_rows
97
+ is_valid = Integer(number_of_rows.to_s) rescue false
98
+ if !is_valid and is_valid <= 0
99
+ raise SearchableError, "Argument number_of_rows must be a valid number"
100
+ end
101
+ end
102
+
103
+ # query_param = arguments.split("$$$")
104
+ query_string = nil
105
+
106
+ searchable_columns = YepSearchable.configuration.retrieve_option(self.to_s, 'searchable_columns')
107
+ if not searchable_columns
108
+ raise SearchableError, "Model must have searchable_resource declaration"
109
+ end
110
+
111
+ query_param.each do |arg|
112
+ if query_string.nil?
113
+ query_string = "("
114
+ else
115
+ query_string = "#{query_string} OR ("
116
+ end
117
+
118
+ searchable_columns.each do |column|
119
+ if query_string.last == "("
120
+ query_string = "#{query_string} #{column.to_s} LIKE '%#{arg}%'"
121
+ else
122
+ query_string = "#{query_string} OR #{column.to_s} LIKE '%#{arg}%'"
123
+ end
124
+ end
125
+ query_string = "#{query_string})"
126
+ end
127
+
128
+ if propagate
129
+ relation_query_string = get_where_relations(query_param)
130
+
131
+ if !relation_query_string.nil? and !relation_query_string.empty?
132
+ if !query_string.nil? and !query_string.empty?
133
+ query_string = "#{query_string} OR #{relation_query_string})"
134
+ else
135
+ query_string = "#{relation_query_string}"
136
+ end
137
+ end
138
+
139
+ relation_query_string = get_where_yep_resources(query_param)
140
+
141
+ if !relation_query_string.nil? and !relation_query_string.empty?
142
+ if !query_string.nil? and !query_string.empty?
143
+ query_string = "#{query_string} OR #{relation_query_string})"
144
+ else
145
+ query_string = "#{relation_query_string}"
146
+ end
147
+ end
148
+ end
149
+
150
+ if only_ids
151
+ entity = []
152
+
153
+ if page.nil? or number_of_rows.nil?
154
+ entity = self.select(:id).where(query_string)
155
+ else
156
+ entity = self.select(:id).paginate(page: page, per_page: number_of_rows).where(query_string)
157
+ end
158
+
159
+ return_value = []
160
+
161
+ entity.each do |ent|
162
+ return_value << ent.id
163
+ end
164
+
165
+ return return_value
166
+ else
167
+ return_value = []
168
+ if page.nil? or number_of_rows.nil?
169
+ return_value = self.where(query_string)
170
+ else
171
+ return_value = self.paginate(page: page, per_page: number_of_rows).where(query_string)
172
+ end
173
+ return return_value
174
+ end
175
+
176
+ end
177
+
178
+ private
179
+ def get_where_relations(query_array)
180
+ while query_array.first.instance_of?(Array)
181
+ query_array = query_array.first
182
+ end
183
+
184
+ query_string = ""
185
+
186
+ reflec_keys = self.reflections.keys
187
+
188
+ reflec_keys.each do |reflection_key|
189
+ reflection = self.reflections[reflection_key]
190
+
191
+ if reflection.macro.to_s.eql?("belongs_to") and reflection.class_name.constantize.is_searchable_resource?
192
+ ids = reflection.class_name.constantize.search_only_ids(query_array)
193
+
194
+ if ids.nil? or ids.empty?
195
+ next
196
+ end
197
+
198
+ if query_string.empty?
199
+ query_string = "(#{reflection.foreign_key} IN ("
200
+ else
201
+ query_string = "#{query_string} OR (#{reflection.foreign_key} IN ("
202
+ end
203
+
204
+ ids.each do |entity_id|
205
+ query_string = "#{query_string} #{entity_id},"
206
+ end
207
+
208
+ query_string = query_string[0, query_string.size - 1]
209
+ query_string = "#{query_string})"
210
+ end
211
+ end
212
+
213
+ return query_string
214
+
215
+ end
216
+
217
+ def get_where_yep_resources(query_array)
218
+ while query_array.first.instance_of?(Array)
219
+ query_array = query_array.first
220
+ end
221
+ yep_res = YepSearchable.configuration.retrieve_option(self.to_s, 'yep_resources')
222
+
223
+ if yep_res.nil? or yep_res.empty?
224
+ return nil
225
+ end
226
+
227
+ query_string = ""
228
+
229
+ yep_res.each do |resource|
230
+ if !resource[:class_name].to_s.camelize.constantize.superclass.to_s.eql?"YepSearchable::YepResource"
231
+ raise SearchableError, "Only classes that implement YepResource are allowed"
232
+ end
233
+
234
+ ids = resource[:class_name].to_s.camelize.constantize.search_only_ids(query_array)
235
+
236
+ if ids.nil? or ids.empty?
237
+ next
238
+ end
239
+
240
+ if query_string.empty?
241
+ query_string = "(#{resource[:foreign_key]} IN ("
242
+ else
243
+ query_string = "#{query_string} OR (#{resource[:foreign_key]} IN ("
244
+ end
245
+
246
+ ids.each do |entity_id|
247
+ query_string = "#{query_string} #{entity_id},"
248
+ end
249
+
250
+ query_string = query_string[0, query_string.size - 1]
251
+ query_string = "#{query_string})"
252
+
253
+ end
254
+
255
+ return query_string
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,3 @@
1
+ module YepSearchable
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,104 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'active_resource'
4
+
5
+ module YepSearchable
6
+ class YepResource < ActiveResource::Base
7
+
8
+ class << self
9
+ attr_accessor :search_path, :search_ids_path
10
+ end
11
+
12
+ self.search_path = nil
13
+ self.search_ids_path = nil
14
+
15
+ def self.search_only_ids(*arguments)
16
+ if arguments.nil? or arguments.size == 0
17
+ return nil
18
+ end
19
+
20
+ while arguments.first.instance_of?(Array)
21
+ arguments = arguments.first
22
+ end
23
+
24
+ query_params = nil
25
+
26
+ arguments.each do |arg|
27
+ if query_params.nil?
28
+ query_params = "#{arg}"
29
+ else
30
+ query_params = "#{query_params}$$$#{arg}"
31
+ end
32
+ end
33
+
34
+ begin
35
+ search_route = "search_ids"
36
+ if not self.search_ids_path.nil?
37
+ search_route = self.search_ids_path
38
+ end
39
+
40
+ search_uri = "#{self.site}#{self.element_path(search_route)}?query=#{query_params}"
41
+ search_uri = search_uri.gsub("//", "/")
42
+ search_uri = search_uri.gsub("http:/", "http://")
43
+ search_uri = search_uri.gsub("https:/", "https://")
44
+
45
+ search_result = Net::HTTP.get(URI.parse(search_uri))
46
+
47
+ result = JSON.parse(search_result)
48
+ return result
49
+ rescue => error
50
+ puts "\nError - #{error.message}\n"
51
+ return nil
52
+ ensure
53
+ # finally
54
+ end
55
+
56
+ return nil
57
+ end
58
+
59
+
60
+ def self.search(*arguments)
61
+ if arguments.nil? or arguments.size == 0
62
+ return nil
63
+ end
64
+
65
+ while arguments.first.instance_of?(Array)
66
+ arguments = arguments.first
67
+ end
68
+
69
+ query_params = nil
70
+
71
+ arguments.each do |arg|
72
+ if query_params.nil?
73
+ query_params = "#{arg}"
74
+ else
75
+ query_params = "#{query_params}$$$#{arg}"
76
+ end
77
+ end
78
+
79
+ begin
80
+ search_route = "search_resource"
81
+ if not self.search_path.nil?
82
+ search_route = self.search_path
83
+ end
84
+
85
+ search_uri = "#{self.site}#{self.element_path(search_route)}?query=#{query_params}"
86
+ search_uri = search_uri.gsub("//", "/")
87
+ search_uri = search_uri.gsub("http:/", "http://")
88
+ search_uri = search_uri.gsub("https:/", "https://")
89
+
90
+ search_result = Net::HTTP.get(URI.parse(search_uri))
91
+
92
+ result = JSON.parse(search_result)
93
+ return result
94
+ rescue => error
95
+ puts "\nError - #{error.message}\n"
96
+ return nil
97
+ ensure
98
+ # finally
99
+ end
100
+
101
+ return nil
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,32 @@
1
+ require 'yep_searchable/configuration'
2
+ require 'yep_searchable/yep_resource'
3
+ require 'yep_searchable/searchable_resource'
4
+
5
+ module YepSearchable
6
+ @@configuration = Configuration.new
7
+
8
+ def self.configuration
9
+ @@configuration ||= Configuration.new
10
+ @@configuration
11
+ end
12
+
13
+ def self.configure
14
+ self.configuration ||= Configuration.new
15
+ yield(configuration)
16
+ end
17
+
18
+ def self.included(mod)
19
+ mod.extend(ClassMethods)
20
+ end
21
+
22
+ class << self
23
+ attr_accessor :searchable_class
24
+ end
25
+ end
26
+
27
+ # reopen ActiveRecord and include all the above to make
28
+ # them available to all our models if they want it
29
+
30
+ ActiveRecord::Base.class_eval do
31
+ include YepSearchable::SearchableResource
32
+ end
@@ -0,0 +1,12 @@
1
+ require 'test_helper'
2
+ require 'rails/performance_test_help'
3
+
4
+ class BrowsingTest < ActionDispatch::PerformanceTest
5
+ # Refer to the documentation for all available options
6
+ # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]
7
+ # :output => 'tmp/performance', :formats => [:flat] }
8
+
9
+ def test_homepage
10
+ get '/'
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
7
+ #
8
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
9
+ # -- they do not yet inherit this setting
10
+ fixtures :all
11
+
12
+ # Add more helper methods to be used by all tests here...
13
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yep_searchable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Luiz Santana
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.2.0
30
+ description: Lib for search using ActiveRecord and ActiveResource
31
+ email:
32
+ - luiz.santana@yepdev.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/yep_searchable/configuration.rb
38
+ - lib/yep_searchable/searchable_error.rb
39
+ - lib/yep_searchable/searchable_resource.rb
40
+ - lib/yep_searchable/version.rb
41
+ - lib/yep_searchable/yep_resource.rb
42
+ - lib/yep_searchable.rb
43
+ - MIT-LICENSE
44
+ - Rakefile
45
+ - README.rdoc
46
+ - test/performance/browsing_test.rb
47
+ - test/test_helper.rb
48
+ homepage: http://yepdev.com
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.8.24
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: YepSearchable-0.0.2
72
+ test_files:
73
+ - test/performance/browsing_test.rb
74
+ - test/test_helper.rb