yep_searchable 0.0.2

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/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