searchgasm 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +7 -1
- data/Manifest +4 -0
- data/README.rdoc +29 -13
- data/benchmarks/benchmark.rb +43 -0
- data/benchmarks/benchmark_helper.rb +52 -0
- data/benchmarks/profile.rb +15 -0
- data/lib/searchgasm/active_record/base.rb +3 -1
- data/lib/searchgasm/conditions/base.rb +16 -18
- data/lib/searchgasm/core_ext/hash.rb +6 -0
- data/lib/searchgasm/search/base.rb +32 -17
- data/lib/searchgasm/search/conditions.rb +1 -0
- data/lib/searchgasm/search/ordering.rb +8 -4
- data/lib/searchgasm/search/protection.rb +1 -13
- data/lib/searchgasm/version.rb +1 -1
- data/lib/searchgasm.rb +6 -0
- data/searchgasm.gemspec +8 -3
- data/test/test_config.rb +34 -0
- data/test/test_search_base.rb +4 -4
- metadata +7 -2
data/CHANGELOG.rdoc
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
== 1.0.
|
1
|
+
== 1.0.2 released 2008-09-12
|
2
|
+
|
3
|
+
* Moved cached searchers out of the global namespace and into the Searchgasm::Cache namespce.
|
4
|
+
* Various changes to improve performance. Added in benchmark reports in readme as well as a benchmarks directory.
|
5
|
+
* Config.per_page works with new_search & new_search! only. Where as before it was only working if the search was protected.
|
6
|
+
|
7
|
+
== 1.0.1 released 2008-09-11
|
2
8
|
|
3
9
|
* Cached "searchers" so when a new search object is instantiated it doesn't go through all of the meta programming and method creation. Helps a lot with performance. You will see the speed benefits after the first instantiation.
|
4
10
|
* Added in new options for page_links.
|
data/Manifest
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
benchmarks/benchmark.rb
|
2
|
+
benchmarks/benchmark_helper.rb
|
3
|
+
benchmarks/profile.rb
|
1
4
|
CHANGELOG.rdoc
|
2
5
|
examples/README.rdoc
|
3
6
|
init.rb
|
@@ -57,6 +60,7 @@ test/test_condition_base.rb
|
|
57
60
|
test/test_condition_types.rb
|
58
61
|
test/test_conditions_base.rb
|
59
62
|
test/test_conditions_protection.rb
|
63
|
+
test/test_config.rb
|
60
64
|
test/test_helper.rb
|
61
65
|
test/test_search_base.rb
|
62
66
|
test/test_search_conditions.rb
|
data/README.rdoc
CHANGED
@@ -55,7 +55,7 @@ In both examples, instead of using the "all" method you could use any search met
|
|
55
55
|
|
56
56
|
== The beauty of searchgasm, integration into rails
|
57
57
|
|
58
|
-
Using Searchgasm in rails is the best part, because rails has all kinds of nifty methods to make dealing with ActiveRecord objects quick and easy, especially with forms. So let's take advantage of them! That's the idea behind this plugin. Searchgasm is searching, ordering, and pagination all rolled into one simple plugin. Take all of that pagination and searching cruft out of your models and let Searchgasm handle it. Check it out:
|
58
|
+
Using Searchgasm in rails is the best part, because rails has all kinds of nifty methods to make dealing with ActiveRecord objects quick and easy, especially with forms. So let's take advantage of them! That's the idea behind this plugin. Searchgasm is searching, ordering, and pagination all rolled into one simple plugin. Take all of that pagination and searching cruft out of your models and controllers, and let Searchgasm handle it. Check it out:
|
59
59
|
|
60
60
|
# app/controllers/users_controller.rb
|
61
61
|
def index
|
@@ -63,15 +63,7 @@ Using Searchgasm in rails is the best part, because rails has all kinds of nifty
|
|
63
63
|
@users, @users_count = @search.all, @search.count
|
64
64
|
end
|
65
65
|
|
66
|
-
Now your view
|
67
|
-
|
68
|
-
1. Passing a search object right into form\_for and fields\_for
|
69
|
-
2. The built in conditions for each column and how you can traverse the relationships and set conditions on them
|
70
|
-
3. The order_by_link helper
|
71
|
-
4. The page_select and per_page_select helpers
|
72
|
-
5. All of your search logic is in 1 spot: your view. Nice and DRY.
|
73
|
-
|
74
|
-
Your view:
|
66
|
+
Now your view:
|
75
67
|
|
76
68
|
# app/views/users/index.html.haml
|
77
69
|
- form_for @search do |f|
|
@@ -103,6 +95,14 @@ Your view:
|
|
103
95
|
- else
|
104
96
|
No users were found.
|
105
97
|
|
98
|
+
Things to note in this view:
|
99
|
+
|
100
|
+
1. Passing a search object right into form\_for and fields\_for
|
101
|
+
2. The built in conditions for each column and how you can traverse the relationships and set conditions on them
|
102
|
+
3. The order_by_link helper
|
103
|
+
4. The page_select and per_page_select helpers
|
104
|
+
5. All of your search logic is in 1 spot: your view. Nice and DRY.
|
105
|
+
|
106
106
|
<b>See my tutorial on this example: http://www.binarylogic.com/2008/9/7/tutorial-pagination-ordering-and-searching-with-searchgasm</b>
|
107
107
|
|
108
108
|
== Exhaustive Example w/ Object Based Searching (great for form_for or fields_for)
|
@@ -230,7 +230,7 @@ Not only can you use searchgasm when searching, but you can use it when setting
|
|
230
230
|
|
231
231
|
== Always use protection...against SQL injections
|
232
232
|
|
233
|
-
If there is one thing we all know, it's to always use protection against SQL injections. That's why searchgasm protects you by default. The new\_search and new\_conditions methods
|
233
|
+
If there is one thing we all know, it's to always use protection against SQL injections. That's why searchgasm protects you by default. The new\_search and new\_conditions methods protect mass assignments by default (instantiation and search.options = {}). This means that various checks are done to ensure it is not possible to perform any type of SQL injection during mass assignments. But this also limits how you can search, meaning you can't write raw SQL. If you want to be daring and search without protection, all that you have to do is add ! to the end of the methods: new\_search! and new\_conditions!.
|
234
234
|
|
235
235
|
=== Protected from SQL injections
|
236
236
|
|
@@ -296,7 +296,7 @@ I want to use it, so let's add it:
|
|
296
296
|
class << self
|
297
297
|
# I pass you the column, you tell me what you want the method to be called.
|
298
298
|
# If you don't want to add this condition for that column, return nil
|
299
|
-
# It defaults to "#{column.name}_sounds_like". So if thats what you want you don't even need to do this.
|
299
|
+
# It defaults to "#{column.name}_sounds_like" (using the class name). So if thats what you want you don't even need to do this.
|
300
300
|
def name_for_column(column)
|
301
301
|
super
|
302
302
|
end
|
@@ -309,7 +309,8 @@ I want to use it, so let's add it:
|
|
309
309
|
|
310
310
|
# You can return an array or a string. NOT a hash, because all of these conditions
|
311
311
|
# need to eventually get merged together. The array or string can be anything you would put in
|
312
|
-
# the :conditions option for ActiveRecord::Base.find()
|
312
|
+
# the :conditions option for ActiveRecord::Base.find(). Also, for a list of methods / variables you can use check out
|
313
|
+
# Searchgasm::Condition::Base.
|
313
314
|
def to_conditions(value)
|
314
315
|
["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
|
315
316
|
end
|
@@ -329,6 +330,21 @@ Pretty nifty, huh? You can create any condition ultimately creating any SQL you
|
|
329
330
|
|
330
331
|
I'm a big fan of understanding what I'm using, so here's a quick explanation: The design behind this plugin is pretty simple. The search object "sanitizes" down into the options passed into ActiveRecord::Base.find(). It serves as a transparent filter between you and ActiveRecord::Base.find(). This filter provides "enhancements" that get translated into options that ActiveRecord::Base.find() can understand. It doesn't dig into the ActiveRecord internals, it only uses what is publicly available. It jumps in and helps out <em>only</em> when needed, otherwise it sits back and lets ActiveRecord do all of the work. Between that and the extensive tests, this is a solid and fast plugin.
|
331
332
|
|
333
|
+
== Performance / Benchmarking
|
334
|
+
|
335
|
+
I ran searchgasm through some performance tests using ruby-prof. After working on it for a little while I improved performance quite a bit. Notice the "2nd instantiation" report. This is implementing caching and skips all dynamic method creation / meta programming. It resulted in code over 50 times faster.
|
336
|
+
|
337
|
+
user system total real
|
338
|
+
1st instantiation: 0.000000 0.000000 0.000000 ( 0.005466)
|
339
|
+
2nd instantiation: 0.000000 0.000000 0.000000 ( 0.000108)
|
340
|
+
Local ordering: 0.000000 0.000000 0.000000 ( 0.000265)
|
341
|
+
Advanced ordering: 0.000000 0.000000 0.000000 ( 0.000413)
|
342
|
+
Local conditions: 0.000000 0.000000 0.000000 ( 0.000241)
|
343
|
+
Advanced conditions: 0.000000 0.000000 0.000000 ( 0.000602)
|
344
|
+
Its complicated: 0.000000 0.000000 0.000000 ( 0.001017)
|
345
|
+
|
346
|
+
I also included the benchmarking file in benchmarks/benchmark.rb to see for yourself.
|
347
|
+
|
332
348
|
== Reporting problems / bugs
|
333
349
|
|
334
350
|
http://binarylogic.lighthouseapp.com/projects/16601-searchgasm
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/benchmark_helper.rb'
|
2
|
+
|
3
|
+
times = 1
|
4
|
+
|
5
|
+
Benchmark.bm(20) do |x|
|
6
|
+
x.report("1st instantiation:") { Account.new_search }
|
7
|
+
x.report("2nd instantiation:") { Account.new_search }
|
8
|
+
|
9
|
+
# Now that we see the benefits of caching, lets cache the rest of the classes and perform the rest of the tests,
|
10
|
+
# so that they are fair
|
11
|
+
User.new_search
|
12
|
+
Order.new_search
|
13
|
+
|
14
|
+
x.report("Local ordering:") do
|
15
|
+
times.times do
|
16
|
+
Account.new_search(:order_by => :name).sanitize
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
x.report("Advanced ordering:") do
|
21
|
+
times.times do
|
22
|
+
Account.new_search(:order_by => {:users => {:orders => :total}}).sanitize
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
x.report("Local conditions:") do
|
27
|
+
times.times do
|
28
|
+
Account.new_search(:conditions => {:name_like => "Binary"}).sanitize
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
x.report("Advanced conditions:") do
|
33
|
+
times.times do
|
34
|
+
Account.new_search(:conditions => {:users => {:orders => {:total_gt => 1}}}).sanitize
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
x.report("Its complicated:") do
|
39
|
+
times.times do
|
40
|
+
Account.new_search(:conditions => {:users => {:orders => {:total_gt => 1, :created_at_after => Time.now}, :first_name_like => "Ben"}, :name_begins_with => "Awesome"}, :per_page => 20, :page => 2, :order_by => {:users => {:orders => :total}}, :order_as => "ASC").sanitize
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "benchmark"
|
3
|
+
require "ruby-prof"
|
4
|
+
require "activerecord"
|
5
|
+
require File.dirname(__FILE__) + '/../test/libs/acts_as_tree'
|
6
|
+
require File.dirname(__FILE__) + '/../lib/searchgasm'
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
9
|
+
|
10
|
+
ActiveRecord::Schema.define(:version => 1) do
|
11
|
+
create_table :accounts do |t|
|
12
|
+
t.datetime :created_at
|
13
|
+
t.datetime :updated_at
|
14
|
+
t.string :name
|
15
|
+
t.boolean :active
|
16
|
+
end
|
17
|
+
|
18
|
+
create_table :users do |t|
|
19
|
+
t.datetime :created_at
|
20
|
+
t.datetime :updated_at
|
21
|
+
t.integer :account_id
|
22
|
+
t.integer :parent_id
|
23
|
+
t.string :first_name
|
24
|
+
t.string :last_name
|
25
|
+
t.boolean :active
|
26
|
+
t.text :bio
|
27
|
+
end
|
28
|
+
|
29
|
+
create_table :orders do |t|
|
30
|
+
t.datetime :created_at
|
31
|
+
t.datetime :updated_at
|
32
|
+
t.integer :user_id
|
33
|
+
t.float :total
|
34
|
+
t.text :description
|
35
|
+
t.binary :receipt
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Account < ActiveRecord::Base
|
40
|
+
has_many :users, :dependent => :destroy
|
41
|
+
has_many :orders, :through => :users
|
42
|
+
end
|
43
|
+
|
44
|
+
class User < ActiveRecord::Base
|
45
|
+
acts_as_tree
|
46
|
+
belongs_to :account
|
47
|
+
has_many :orders, :dependent => :destroy
|
48
|
+
end
|
49
|
+
|
50
|
+
class Order < ActiveRecord::Base
|
51
|
+
belongs_to :user
|
52
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/benchmark_helper.rb'
|
2
|
+
require "ruby-prof"
|
3
|
+
|
4
|
+
Account.new_search
|
5
|
+
User.new_search
|
6
|
+
Order.new_search
|
7
|
+
|
8
|
+
RubyProf.start
|
9
|
+
|
10
|
+
# Put profile code here
|
11
|
+
|
12
|
+
result = RubyProf.stop
|
13
|
+
|
14
|
+
printer = RubyProf::FlatPrinter.new(result)
|
15
|
+
printer.print(STDOUT, 0)
|
@@ -118,7 +118,9 @@ module Searchgasm
|
|
118
118
|
private
|
119
119
|
def sanitize_options_with_searchgasm(options = {})
|
120
120
|
return options unless Searchgasm::Search::Base.needed?(self, options)
|
121
|
-
searchgasm_searcher(options)
|
121
|
+
search = searchgasm_searcher(options)
|
122
|
+
search.acting_as_filter = true
|
123
|
+
search.sanitize
|
122
124
|
end
|
123
125
|
|
124
126
|
def searchgasm_conditions(options = {})
|
@@ -22,7 +22,7 @@ module Searchgasm
|
|
22
22
|
# class << self
|
23
23
|
# # I pass you the column, you tell me what you want the method to be called.
|
24
24
|
# # If you don't want to add this condition for that column, return nil
|
25
|
-
# # It defaults to "#{column.name}_sounds_like". So if thats what you want you don't even need to do this.
|
25
|
+
# # It defaults to "#{column.name}_sounds_like" (using the class name). So if thats what you want you don't even need to do this.
|
26
26
|
# def name_for_column(column)
|
27
27
|
# super
|
28
28
|
# end
|
@@ -35,7 +35,7 @@ module Searchgasm
|
|
35
35
|
#
|
36
36
|
# # You can return an array or a string. NOT a hash, because all of these conditions
|
37
37
|
# # need to eventually get merged together. The array or string can be anything you would put in
|
38
|
-
# # the :conditions option for ActiveRecord::Base.find()
|
38
|
+
# # the :conditions option for ActiveRecord::Base.find(). Also, for a list of methods / variables you can use check out earchgasm::Condition::Base
|
39
39
|
# def to_conditions(value)
|
40
40
|
# ["#{quoted_table_name}.#{quoted_column_name} SOUNDS LIKE ?", value]
|
41
41
|
# end
|
@@ -75,24 +75,26 @@ module Searchgasm
|
|
75
75
|
# Creates virtual classes for the class passed to it. This is a neccesity for keeping dynamically created method
|
76
76
|
# names specific to models. It provides caching and helps a lot with performance.
|
77
77
|
def create_virtual_class(model_class)
|
78
|
-
class_search_name = "::#{model_class.name}Conditions"
|
78
|
+
class_search_name = "::Searchgasm::Cache::#{model_class.name}Conditions"
|
79
79
|
|
80
80
|
begin
|
81
|
-
class_search_name
|
81
|
+
eval(class_search_name)
|
82
82
|
rescue NameError
|
83
83
|
eval <<-end_eval
|
84
|
-
class #{class_search_name} < ::Searchgasm::Conditions::Base
|
84
|
+
class #{class_search_name} < ::Searchgasm::Conditions::Base
|
85
|
+
def self.klass
|
86
|
+
#{model_class.name}
|
87
|
+
end
|
88
|
+
|
89
|
+
def klass
|
90
|
+
#{model_class.name}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#{class_search_name}
|
85
95
|
end_eval
|
86
|
-
|
87
|
-
class_search_name.constantize
|
88
96
|
end
|
89
97
|
end
|
90
|
-
|
91
|
-
# The class / model we are searching
|
92
|
-
def klass
|
93
|
-
# Can't cache this because thin and mongrel don't play nice with caching constants
|
94
|
-
name.split("::").last.gsub(/Conditions$/, "").constantize
|
95
|
-
end
|
96
98
|
end
|
97
99
|
|
98
100
|
def initialize(init_conditions = {})
|
@@ -123,10 +125,6 @@ module Searchgasm
|
|
123
125
|
i.blank? ? nil : (i.size == 1 ? i.first : i)
|
124
126
|
end
|
125
127
|
|
126
|
-
def klass
|
127
|
-
self.class.klass
|
128
|
-
end
|
129
|
-
|
130
128
|
# Sanitizes the conditions down into conditions that ActiveRecord::Base.find can understand.
|
131
129
|
def sanitize
|
132
130
|
conditions = merge_conditions(*objects.collect { |object| object.sanitize })
|
@@ -244,7 +242,7 @@ module Searchgasm
|
|
244
242
|
end
|
245
243
|
|
246
244
|
def assert_valid_conditions(conditions)
|
247
|
-
conditions.stringify_keys.
|
245
|
+
conditions.stringify_keys.fast_assert_valid_keys(self.class.condition_names + self.class.association_names)
|
248
246
|
end
|
249
247
|
|
250
248
|
def associations
|
@@ -15,6 +15,12 @@ module Searchgasm
|
|
15
15
|
|
16
16
|
new_hash
|
17
17
|
end
|
18
|
+
|
19
|
+
# assert_valid_keys was killing performance. Array.flatten was the culprit, so I rewrote this method, got a 35% performance increase
|
20
|
+
def fast_assert_valid_keys(valid_keys)
|
21
|
+
unknown_keys = keys - valid_keys
|
22
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
23
|
+
end
|
18
24
|
end
|
19
25
|
end
|
20
26
|
end
|
@@ -32,24 +32,27 @@ module Searchgasm #:nodoc:
|
|
32
32
|
# Creates virtual classes for the class passed to it. This is a neccesity for keeping dynamically created method
|
33
33
|
# names specific to models. It provides caching and helps a lot with performance.
|
34
34
|
def create_virtual_class(model_class)
|
35
|
-
class_search_name = "::#{model_class.name}Search"
|
35
|
+
class_search_name = "::Searchgasm::Cache::#{model_class.name}Search"
|
36
36
|
|
37
37
|
begin
|
38
|
-
class_search_name
|
38
|
+
eval(class_search_name)
|
39
39
|
rescue NameError
|
40
|
+
# The method definitions are for performance, bottlenecks found with ruby-prof
|
40
41
|
eval <<-end_eval
|
41
|
-
class #{class_search_name} < ::Searchgasm::Search::Base
|
42
|
+
class #{class_search_name} < ::Searchgasm::Search::Base
|
43
|
+
def self.klass
|
44
|
+
#{model_class.name}
|
45
|
+
end
|
46
|
+
|
47
|
+
def klass
|
48
|
+
#{model_class.name}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#{class_search_name}
|
42
53
|
end_eval
|
43
|
-
|
44
|
-
class_search_name.constantize
|
45
54
|
end
|
46
55
|
end
|
47
|
-
|
48
|
-
# The class / model we are searching
|
49
|
-
def klass
|
50
|
-
# Can't cache this because thin and mongrel don't play nice with caching constants
|
51
|
-
name.split("::").last.gsub(/Search$/, "").constantize
|
52
|
-
end
|
53
56
|
end
|
54
57
|
|
55
58
|
def initialize(init_options = {})
|
@@ -67,28 +70,40 @@ module Searchgasm #:nodoc:
|
|
67
70
|
end_eval
|
68
71
|
end
|
69
72
|
|
73
|
+
# Flag to determine if searchgasm is acting as a filter for the ActiveRecord search methods.
|
74
|
+
# The purpose of this is to determine if Config.per_page should be implemented.
|
75
|
+
def acting_as_filter=(value)
|
76
|
+
@acting_as_filter == true
|
77
|
+
end
|
78
|
+
|
79
|
+
# See acting_as_filter=
|
80
|
+
def acting_as_filter?
|
81
|
+
@acting_as_filter == true
|
82
|
+
end
|
83
|
+
|
70
84
|
# Makes using searchgasm in the console less annoying and keeps the output meaningful and useful
|
71
85
|
def inspect
|
72
86
|
options_as_nice_string = ::ActiveRecord::Base.valid_find_options.collect { |name| "#{name}: #{send(name)}" }.join(", ")
|
73
87
|
"#<#{klass} #{options_as_nice_string}>"
|
74
88
|
end
|
75
89
|
|
76
|
-
# Convenience method for self.class.klass
|
77
|
-
def klass
|
78
|
-
self.class.klass
|
79
|
-
end
|
80
|
-
|
81
90
|
def limit=(value)
|
91
|
+
@set_limit = true
|
82
92
|
@limit = value.blank? || value == 0 ? nil : value.to_i
|
83
93
|
end
|
84
94
|
|
95
|
+
def limit
|
96
|
+
@limit ||= Config.per_page if !acting_as_filter? && !@set_limit
|
97
|
+
@limit
|
98
|
+
end
|
99
|
+
|
85
100
|
def offset=(value)
|
86
101
|
@offset = value.to_i
|
87
102
|
end
|
88
103
|
|
89
104
|
def options=(values)
|
90
105
|
return unless values.is_a?(Hash)
|
91
|
-
values.symbolize_keys.
|
106
|
+
values.symbolize_keys.fast_assert_valid_keys(VALID_FIND_OPTIONS)
|
92
107
|
values.each { |option, value| send("#{option}=", value) }
|
93
108
|
end
|
94
109
|
|
@@ -33,6 +33,7 @@ module Searchgasm
|
|
33
33
|
# now you can create the rest of your search and your "scope" will get merged into your final SQL.
|
34
34
|
# What this does is determine if the value a hash or a conditions object, if not it sets it up as a scope.
|
35
35
|
def conditions_with_conditions=(values)
|
36
|
+
|
36
37
|
case values
|
37
38
|
when Searchgasm::Conditions::Base
|
38
39
|
@conditions = values
|
@@ -99,7 +99,7 @@ module Searchgasm
|
|
99
99
|
# order_by = :id # => users.id ASC
|
100
100
|
# order_by = [:id, name] # => users.id ASC, user.name ASC
|
101
101
|
# order_by = [:id, {:user_group => :name}] # => users.id ASC, user_groups.name ASC
|
102
|
-
def order_by=(value)
|
102
|
+
def order_by=(value)
|
103
103
|
@order_by = get_order_by_value(value)
|
104
104
|
@order = order_by_to_order(@order_by, order_as) # use @order so @order_by doesnt get reset
|
105
105
|
@order_by
|
@@ -126,7 +126,7 @@ module Searchgasm
|
|
126
126
|
k = order_by.keys.first
|
127
127
|
v = order_by.values.first
|
128
128
|
new_includes << k.to_sym
|
129
|
-
sql_parts << order_by_to_order(v, order_as, k.to_s.classify
|
129
|
+
sql_parts << order_by_to_order(v, order_as, eval(k.to_s.classify), new_includes) # using eval, better performance, protection makes sure nothing fishy goes on here
|
130
130
|
when Symbol, String
|
131
131
|
new_include = build_order_by_includes(new_includes)
|
132
132
|
self.order_by_includes << new_include if new_include
|
@@ -149,11 +149,15 @@ module Searchgasm
|
|
149
149
|
end
|
150
150
|
|
151
151
|
def quote_column_name(column_name)
|
152
|
-
|
152
|
+
klass_connection.quote_column_name(column_name)
|
153
153
|
end
|
154
154
|
|
155
155
|
def quote_table_name(table_name)
|
156
|
-
|
156
|
+
klass_connection.quote_table_name(table_name)
|
157
|
+
end
|
158
|
+
|
159
|
+
def klass_connection
|
160
|
+
@connection ||= klass.connection
|
157
161
|
end
|
158
162
|
end
|
159
163
|
end
|
@@ -30,22 +30,10 @@ module Searchgasm
|
|
30
30
|
def self.included(klass)
|
31
31
|
klass.class_eval do
|
32
32
|
attr_reader :protect
|
33
|
-
alias_method_chain :limit, :protection
|
34
|
-
alias_method_chain :limit=, :protection
|
35
33
|
alias_method_chain :options=, :protection
|
36
34
|
end
|
37
35
|
end
|
38
36
|
|
39
|
-
def limit_with_protection # :nodoc:
|
40
|
-
return Config.per_page if protected? && !@set_limit
|
41
|
-
limit_without_protection
|
42
|
-
end
|
43
|
-
|
44
|
-
def limit_with_protection=(value) # :nodoc:
|
45
|
-
@set_limit = true
|
46
|
-
self.limit_without_protection = value
|
47
|
-
end
|
48
|
-
|
49
37
|
def options_with_protection=(values) # :nodoc:
|
50
38
|
return unless values.is_a?(Hash)
|
51
39
|
self.protect = values.delete(:protect) if values.has_key?(:protect) # make sure we do this first
|
@@ -88,7 +76,7 @@ module Searchgasm
|
|
88
76
|
end
|
89
77
|
|
90
78
|
def frisk!(options)
|
91
|
-
options.symbolize_keys.
|
79
|
+
options.symbolize_keys.fast_assert_valid_keys(SAFE_OPTIONS)
|
92
80
|
raise(ArgumentError, ":order_by can only contain colum names in the string, hash, or array format") unless order_by_safe?(get_order_by_value(options[:order_by]))
|
93
81
|
end
|
94
82
|
end
|
data/lib/searchgasm/version.rb
CHANGED
data/lib/searchgasm.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
1
3
|
require "active_record"
|
2
4
|
require "active_support"
|
3
5
|
|
@@ -72,4 +74,8 @@ module Searchgasm
|
|
72
74
|
Base.register_condition("Searchgasm::Condition::#{condition.to_s.camelize}".constantize)
|
73
75
|
end
|
74
76
|
end
|
77
|
+
|
78
|
+
# The namespace I put all cached search classes.
|
79
|
+
module Cache
|
80
|
+
end
|
75
81
|
end
|
data/searchgasm.gemspec
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
|
2
|
-
# Gem::Specification for Searchgasm-1.0.
|
2
|
+
# Gem::Specification for Searchgasm-1.0.2
|
3
3
|
# Originally generated by Echoe
|
4
4
|
|
5
5
|
--- !ruby/object:Gem::Specification
|
6
6
|
name: searchgasm
|
7
7
|
version: !ruby/object:Gem::Version
|
8
|
-
version: 1.0.
|
8
|
+
version: 1.0.2
|
9
9
|
platform: ruby
|
10
10
|
authors:
|
11
11
|
- Ben Johnson of Binary Logic
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
|
15
|
-
date: 2008-09-
|
15
|
+
date: 2008-09-12 00:00:00 -04:00
|
16
16
|
default_executable:
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
@@ -96,6 +96,9 @@ extra_rdoc_files:
|
|
96
96
|
- lib/searchgasm.rb
|
97
97
|
- README.rdoc
|
98
98
|
files:
|
99
|
+
- benchmarks/benchmark.rb
|
100
|
+
- benchmarks/benchmark_helper.rb
|
101
|
+
- benchmarks/profile.rb
|
99
102
|
- CHANGELOG.rdoc
|
100
103
|
- examples/README.rdoc
|
101
104
|
- init.rb
|
@@ -155,6 +158,7 @@ files:
|
|
155
158
|
- test/test_condition_types.rb
|
156
159
|
- test/test_conditions_base.rb
|
157
160
|
- test/test_conditions_protection.rb
|
161
|
+
- test/test_config.rb
|
158
162
|
- test/test_helper.rb
|
159
163
|
- test/test_search_base.rb
|
160
164
|
- test/test_search_conditions.rb
|
@@ -200,6 +204,7 @@ test_files:
|
|
200
204
|
- test/test_condition_types.rb
|
201
205
|
- test/test_conditions_base.rb
|
202
206
|
- test/test_conditions_protection.rb
|
207
|
+
- test/test_config.rb
|
203
208
|
- test/test_helper.rb
|
204
209
|
- test/test_search_base.rb
|
205
210
|
- test/test_search_conditions.rb
|
data/test/test_config.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class TestConfig < Test::Unit::TestCase
|
4
|
+
fixtures :accounts, :users, :orders
|
5
|
+
|
6
|
+
def setup
|
7
|
+
setup_db
|
8
|
+
load_fixtures
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
teardown_db
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_per_page
|
16
|
+
Searchgasm::Config.per_page = 1
|
17
|
+
|
18
|
+
assert Account.count > 1
|
19
|
+
assert Account.all.size > 1
|
20
|
+
assert User.all.size > 1
|
21
|
+
assert User.find(:all, :per_page => 1).size == 1
|
22
|
+
assert User.new_search.all.size == 1
|
23
|
+
assert User.new_search(:per_page => nil).all.size > 1
|
24
|
+
|
25
|
+
Searchgasm::Config.per_page = nil
|
26
|
+
|
27
|
+
assert Account.count > 1
|
28
|
+
assert Account.all.size > 1
|
29
|
+
assert User.all.size > 1
|
30
|
+
assert User.find(:all, :per_page => 1).size == 1
|
31
|
+
assert User.new_search.all.size > 1
|
32
|
+
assert User.new_search(:per_page => 1).all.size == 1
|
33
|
+
end
|
34
|
+
end
|
data/test/test_search_base.rb
CHANGED
@@ -150,10 +150,10 @@ class TestSearchBase < Test::Unit::TestCase
|
|
150
150
|
def test_searching
|
151
151
|
search = Account.new_search
|
152
152
|
search.conditions.name_like = "Binary"
|
153
|
-
assert_equal
|
154
|
-
assert_equal
|
155
|
-
assert_equal
|
156
|
-
assert_equal
|
153
|
+
assert_equal [Account.find(1), Account.find(3)], search.all
|
154
|
+
assert_equal [Account.find(1), Account.find(3)], search.find(:all)
|
155
|
+
assert_equal Account.find(1), search.first
|
156
|
+
assert_equal Account.find(1), search.find(:first)
|
157
157
|
|
158
158
|
search.per_page = 20
|
159
159
|
search.page = 2
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: searchgasm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Johnson of Binary Logic
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-09-
|
12
|
+
date: 2008-09-12 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -93,6 +93,9 @@ extra_rdoc_files:
|
|
93
93
|
- lib/searchgasm.rb
|
94
94
|
- README.rdoc
|
95
95
|
files:
|
96
|
+
- benchmarks/benchmark.rb
|
97
|
+
- benchmarks/benchmark_helper.rb
|
98
|
+
- benchmarks/profile.rb
|
96
99
|
- CHANGELOG.rdoc
|
97
100
|
- examples/README.rdoc
|
98
101
|
- init.rb
|
@@ -152,6 +155,7 @@ files:
|
|
152
155
|
- test/test_condition_types.rb
|
153
156
|
- test/test_conditions_base.rb
|
154
157
|
- test/test_conditions_protection.rb
|
158
|
+
- test/test_config.rb
|
155
159
|
- test/test_helper.rb
|
156
160
|
- test/test_search_base.rb
|
157
161
|
- test/test_search_conditions.rb
|
@@ -198,6 +202,7 @@ test_files:
|
|
198
202
|
- test/test_condition_types.rb
|
199
203
|
- test/test_conditions_base.rb
|
200
204
|
- test/test_conditions_protection.rb
|
205
|
+
- test/test_config.rb
|
201
206
|
- test/test_helper.rb
|
202
207
|
- test/test_search_base.rb
|
203
208
|
- test/test_search_conditions.rb
|