searchlogic 1.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +228 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +123 -0
- data/README.rdoc +383 -0
- data/Rakefile +15 -0
- data/TODO.rdoc +6 -0
- data/examples/README.rdoc +4 -0
- data/init.rb +1 -0
- data/lib/searchlogic.rb +89 -0
- data/lib/searchlogic/active_record/associations.rb +52 -0
- data/lib/searchlogic/active_record/base.rb +218 -0
- data/lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb +172 -0
- data/lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb +168 -0
- data/lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb +75 -0
- data/lib/searchlogic/condition/base.rb +159 -0
- data/lib/searchlogic/condition/begins_with.rb +17 -0
- data/lib/searchlogic/condition/blank.rb +21 -0
- data/lib/searchlogic/condition/child_of.rb +11 -0
- data/lib/searchlogic/condition/descendant_of.rb +24 -0
- data/lib/searchlogic/condition/ends_with.rb +17 -0
- data/lib/searchlogic/condition/equals.rb +27 -0
- data/lib/searchlogic/condition/greater_than.rb +15 -0
- data/lib/searchlogic/condition/greater_than_or_equal_to.rb +15 -0
- data/lib/searchlogic/condition/inclusive_descendant_of.rb +11 -0
- data/lib/searchlogic/condition/keywords.rb +47 -0
- data/lib/searchlogic/condition/less_than.rb +15 -0
- data/lib/searchlogic/condition/less_than_or_equal_to.rb +15 -0
- data/lib/searchlogic/condition/like.rb +15 -0
- data/lib/searchlogic/condition/nil.rb +21 -0
- data/lib/searchlogic/condition/not_begin_with.rb +20 -0
- data/lib/searchlogic/condition/not_blank.rb +19 -0
- data/lib/searchlogic/condition/not_end_with.rb +20 -0
- data/lib/searchlogic/condition/not_equal.rb +26 -0
- data/lib/searchlogic/condition/not_have_keywords.rb +20 -0
- data/lib/searchlogic/condition/not_like.rb +20 -0
- data/lib/searchlogic/condition/not_nil.rb +19 -0
- data/lib/searchlogic/condition/sibling_of.rb +14 -0
- data/lib/searchlogic/condition/tree.rb +17 -0
- data/lib/searchlogic/conditions/base.rb +484 -0
- data/lib/searchlogic/conditions/protection.rb +36 -0
- data/lib/searchlogic/config.rb +31 -0
- data/lib/searchlogic/config/helpers.rb +289 -0
- data/lib/searchlogic/config/search.rb +53 -0
- data/lib/searchlogic/core_ext/hash.rb +75 -0
- data/lib/searchlogic/helpers/control_types/link.rb +310 -0
- data/lib/searchlogic/helpers/control_types/links.rb +241 -0
- data/lib/searchlogic/helpers/control_types/remote_link.rb +87 -0
- data/lib/searchlogic/helpers/control_types/remote_links.rb +72 -0
- data/lib/searchlogic/helpers/control_types/remote_select.rb +36 -0
- data/lib/searchlogic/helpers/control_types/select.rb +82 -0
- data/lib/searchlogic/helpers/form.rb +208 -0
- data/lib/searchlogic/helpers/utilities.rb +197 -0
- data/lib/searchlogic/modifiers/absolute.rb +15 -0
- data/lib/searchlogic/modifiers/acos.rb +11 -0
- data/lib/searchlogic/modifiers/asin.rb +11 -0
- data/lib/searchlogic/modifiers/atan.rb +11 -0
- data/lib/searchlogic/modifiers/base.rb +27 -0
- data/lib/searchlogic/modifiers/ceil.rb +15 -0
- data/lib/searchlogic/modifiers/char_length.rb +15 -0
- data/lib/searchlogic/modifiers/cos.rb +15 -0
- data/lib/searchlogic/modifiers/cot.rb +15 -0
- data/lib/searchlogic/modifiers/day_of_month.rb +15 -0
- data/lib/searchlogic/modifiers/day_of_week.rb +15 -0
- data/lib/searchlogic/modifiers/day_of_year.rb +15 -0
- data/lib/searchlogic/modifiers/degrees.rb +11 -0
- data/lib/searchlogic/modifiers/exp.rb +15 -0
- data/lib/searchlogic/modifiers/floor.rb +15 -0
- data/lib/searchlogic/modifiers/hex.rb +11 -0
- data/lib/searchlogic/modifiers/hour.rb +11 -0
- data/lib/searchlogic/modifiers/log.rb +15 -0
- data/lib/searchlogic/modifiers/log10.rb +11 -0
- data/lib/searchlogic/modifiers/log2.rb +11 -0
- data/lib/searchlogic/modifiers/lower.rb +15 -0
- data/lib/searchlogic/modifiers/ltrim.rb +15 -0
- data/lib/searchlogic/modifiers/md5.rb +11 -0
- data/lib/searchlogic/modifiers/microseconds.rb +11 -0
- data/lib/searchlogic/modifiers/milliseconds.rb +11 -0
- data/lib/searchlogic/modifiers/minute.rb +15 -0
- data/lib/searchlogic/modifiers/month.rb +15 -0
- data/lib/searchlogic/modifiers/octal.rb +15 -0
- data/lib/searchlogic/modifiers/radians.rb +11 -0
- data/lib/searchlogic/modifiers/round.rb +11 -0
- data/lib/searchlogic/modifiers/rtrim.rb +15 -0
- data/lib/searchlogic/modifiers/second.rb +15 -0
- data/lib/searchlogic/modifiers/sign.rb +11 -0
- data/lib/searchlogic/modifiers/sin.rb +11 -0
- data/lib/searchlogic/modifiers/square_root.rb +15 -0
- data/lib/searchlogic/modifiers/tan.rb +15 -0
- data/lib/searchlogic/modifiers/trim.rb +15 -0
- data/lib/searchlogic/modifiers/upper.rb +15 -0
- data/lib/searchlogic/modifiers/week.rb +11 -0
- data/lib/searchlogic/modifiers/year.rb +11 -0
- data/lib/searchlogic/search/base.rb +148 -0
- data/lib/searchlogic/search/conditions.rb +53 -0
- data/lib/searchlogic/search/ordering.rb +244 -0
- data/lib/searchlogic/search/pagination.rb +121 -0
- data/lib/searchlogic/search/protection.rb +89 -0
- data/lib/searchlogic/search/searching.rb +31 -0
- data/lib/searchlogic/shared/utilities.rb +50 -0
- data/lib/searchlogic/shared/virtual_classes.rb +39 -0
- data/lib/searchlogic/version.rb +79 -0
- data/searchlogic.gemspec +39 -0
- data/test/fixtures/accounts.yml +15 -0
- data/test/fixtures/cats.yml +3 -0
- data/test/fixtures/dogs.yml +3 -0
- data/test/fixtures/orders.yml +14 -0
- data/test/fixtures/user_groups.yml +13 -0
- data/test/fixtures/users.yml +36 -0
- data/test/test_active_record_associations.rb +81 -0
- data/test/test_active_record_base.rb +93 -0
- data/test/test_condition_base.rb +52 -0
- data/test/test_condition_types.rb +143 -0
- data/test/test_conditions_base.rb +242 -0
- data/test/test_conditions_protection.rb +16 -0
- data/test/test_config.rb +23 -0
- data/test/test_helper.rb +134 -0
- data/test/test_search_base.rb +227 -0
- data/test/test_search_conditions.rb +19 -0
- data/test/test_search_ordering.rb +165 -0
- data/test/test_search_pagination.rb +72 -0
- data/test/test_search_protection.rb +24 -0
- data/test_libs/acts_as_tree.rb +98 -0
- data/test_libs/ordered_hash.rb +9 -0
- data/test_libs/rexml_fix.rb +14 -0
- metadata +317 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module Search
|
3
|
+
# = Searchlogic Conditions
|
4
|
+
#
|
5
|
+
# Implements all of the conditions functionality into a searchlogic search. All of this functonality is extracted out into its own class Searchlogic::Conditions::Base. This is a separate module to help keep the code
|
6
|
+
# clean and organized.
|
7
|
+
module Conditions
|
8
|
+
def self.included(klass)
|
9
|
+
klass.class_eval do
|
10
|
+
alias_method_chain :initialize, :conditions
|
11
|
+
alias_method_chain :conditions=, :conditions
|
12
|
+
alias_method_chain :sanitize, :conditions
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize_with_conditions(init_options = {})
|
17
|
+
self.conditions = Searchlogic::Conditions::Base.create_virtual_class(klass).new
|
18
|
+
initialize_without_conditions(init_options)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sets conditions on the search. Accepts a hash or a Searchlogic::Conditions::Base object.
|
22
|
+
#
|
23
|
+
# === Examples
|
24
|
+
#
|
25
|
+
# search.conditions = {:first_name_like => "Ben"}
|
26
|
+
# search.conditions = User.new_conditions
|
27
|
+
#
|
28
|
+
# or to set a scope
|
29
|
+
#
|
30
|
+
# search.conditions = "user_group_id = 6"
|
31
|
+
#
|
32
|
+
# now you can create the rest of your search and your "scope" will get merged into your final SQL.
|
33
|
+
# What this does is determine if the value a hash or a conditions object, if not it sets it up as a scope.
|
34
|
+
def conditions_with_conditions=(values)
|
35
|
+
case values
|
36
|
+
when Searchlogic::Conditions::Base
|
37
|
+
@conditions = values
|
38
|
+
else
|
39
|
+
@conditions.conditions = values
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def sanitize_with_conditions(searching = true) # :nodoc:
|
44
|
+
find_options = sanitize_without_conditions(searching)
|
45
|
+
if conditions_obj = find_options.delete(:conditions)
|
46
|
+
new_conditions = conditions_obj.sanitize
|
47
|
+
find_options[:conditions] = new_conditions unless new_conditions.blank?
|
48
|
+
end
|
49
|
+
find_options
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module Search
|
3
|
+
# = Search Ordering
|
4
|
+
#
|
5
|
+
# The purpose of this module is to provide easy ordering for your searches. All that these options do is
|
6
|
+
# build :order for you. This plays a huge part in ordering your data on the interface. See the options and examples below. The readme also touches on ordering. It's pretty simple thought:
|
7
|
+
#
|
8
|
+
# === Examples
|
9
|
+
#
|
10
|
+
# search.order_by = :id
|
11
|
+
# search.order_by = [:id, :first_name]
|
12
|
+
# search.order_by = {:user_group => :name}
|
13
|
+
# search.order_by = [:id, {:user_group => :name}]
|
14
|
+
# search.order_by = {:user_group => {:account => :name}} # you can traverse through all of your relationships
|
15
|
+
#
|
16
|
+
# search.order_as = "DESC"
|
17
|
+
# search.order_as = "ASC"
|
18
|
+
module Ordering
|
19
|
+
def self.included(klass)
|
20
|
+
klass.class_eval do
|
21
|
+
alias_method_chain :order=, :ordering
|
22
|
+
alias_method_chain :sanitize, :ordering
|
23
|
+
attr_reader :priority_order
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def order_with_ordering=(value) # :nodoc
|
28
|
+
@order_by = nil
|
29
|
+
@order_as = nil
|
30
|
+
@order_by_auto_joins = nil
|
31
|
+
self.order_without_ordering = value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convenience method for determining if the ordering is ascending
|
35
|
+
def asc?
|
36
|
+
!desc?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Convenience method for determining if the ordering is descending
|
40
|
+
def desc?
|
41
|
+
return false if order_as.nil?
|
42
|
+
order_as == "DESC"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Determines how the search is being ordered: as DESC or ASC
|
46
|
+
def order_as
|
47
|
+
return if order.blank?
|
48
|
+
return @order_as if @order_as
|
49
|
+
|
50
|
+
case order
|
51
|
+
when /ASC$/i
|
52
|
+
@order_as = "ASC"
|
53
|
+
when /DESC$/i
|
54
|
+
@order_as = "DESC"
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Sets how the results will be ordered: ASC or DESC
|
61
|
+
def order_as=(value)
|
62
|
+
value = value.blank? ? nil : value.to_s.upcase
|
63
|
+
raise(ArgumentError, "order_as only accepts a blank string / nil or a string as 'ASC' or 'DESC'") if !value.blank? && !["ASC", "DESC"].include?(value)
|
64
|
+
if @order_by
|
65
|
+
@order = order_by_to_order(@order_by, value)
|
66
|
+
elsif order
|
67
|
+
@order.gsub!(/(ASC|DESC)/i, value)
|
68
|
+
end
|
69
|
+
@order_as = value
|
70
|
+
end
|
71
|
+
|
72
|
+
# Determines by what columns the search is being ordered. This is nifty in that is reverse engineers the order SQL to determine this, only
|
73
|
+
# if you haven't explicitly set the order_by option yourself.
|
74
|
+
def order_by
|
75
|
+
return if order.blank?
|
76
|
+
@order_by ||= order_to_order_by(order)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Lets you set how to order the data
|
80
|
+
#
|
81
|
+
# === Examples
|
82
|
+
#
|
83
|
+
# In these examples "ASC" is determined by the value of order_as
|
84
|
+
#
|
85
|
+
# order_by = :id # => users.id ASC
|
86
|
+
# order_by = [:id, name] # => users.id ASC, user.name ASC
|
87
|
+
# order_by = [:id, {:user_group => :name}] # => users.id ASC, user_groups.name ASC
|
88
|
+
def order_by=(value)
|
89
|
+
@order_by_auto_joins = nil
|
90
|
+
@order_by = get_order_by_value(value)
|
91
|
+
@order = order_by_to_order(@order_by, @order_as)
|
92
|
+
@order_by
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the joins neccessary for the "order" statement so that we don't get an SQL error
|
96
|
+
def order_by_auto_joins
|
97
|
+
@order_by_auto_joins ||= build_order_by_auto_joins(order_by)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Let's you set a priority order. Meaning this will get ordered first before anything else, but is unnoticeable and abstracted out from your regular order. For example, lets say you have a model called Product
|
101
|
+
# that had a "featured" boolean column. You want to order the products by the price, quantity, etc., but you want the featured products to always be first.
|
102
|
+
#
|
103
|
+
# Without a priority order your controller would get cluttered and your code would be much more complicated. All of your order_by_link methods would have to be order_by_link [:featured, :price], :text => "Price"
|
104
|
+
# Your order_by_link methods alternate between ASC and DESC, so the featured products would jump from the top the bottom. It presents a lot of "work arounds". So priority_order solves this.
|
105
|
+
def priority_order=(value)
|
106
|
+
@priority_order = value
|
107
|
+
end
|
108
|
+
|
109
|
+
# Same as order_by but for your priority order. See priority_order= for more informaton on priority_order.
|
110
|
+
def priority_order_by
|
111
|
+
return if priority_order.blank?
|
112
|
+
@priority_order_by ||= order_to_order_by(priority_order)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Same as order_by= but for your priority order. See priority_order= for more informaton on priority_order.
|
116
|
+
def priority_order_by=(value)
|
117
|
+
@priority_order_by_auto_joins = nil
|
118
|
+
@priority_order_by = get_order_by_value(value)
|
119
|
+
@priority_order = order_by_to_order(@priority_order_by, @priority_order_as)
|
120
|
+
@priority_order_by
|
121
|
+
end
|
122
|
+
|
123
|
+
# Same as order_as but for your priority order. See priority_order= for more informaton on priority_order.
|
124
|
+
def priority_order_as
|
125
|
+
return if priority_order.blank?
|
126
|
+
return @priority_order_as if @priority_order_as
|
127
|
+
|
128
|
+
case priority_order
|
129
|
+
when /ASC$/i
|
130
|
+
@priority_order_as = "ASC"
|
131
|
+
when /DESC$/i
|
132
|
+
@priority_order_as = "DESC"
|
133
|
+
else
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Same as order_as= but for your priority order. See priority_order= for more informaton on priority_order.
|
139
|
+
def priority_order_as=(value)
|
140
|
+
value = value.blank? ? nil : value.to_s.upcase
|
141
|
+
raise(ArgumentError, "priority_order_as only accepts a blank string / nil or a string as 'ASC' or 'DESC'") if !value.blank? && !["ASC", "DESC"].include?(value)
|
142
|
+
if @priority_order_by
|
143
|
+
@priority_order = order_by_to_order(@priority_order_by, value)
|
144
|
+
elsif priority_order
|
145
|
+
@priority_order.gsub!(/(ASC|DESC)/i, value)
|
146
|
+
end
|
147
|
+
@priority_order_as = value
|
148
|
+
end
|
149
|
+
|
150
|
+
def priority_order_by_auto_joins
|
151
|
+
@priority_order_by_auto_joins ||= build_order_by_auto_joins(priority_order_by)
|
152
|
+
end
|
153
|
+
|
154
|
+
def sanitize_with_ordering(searching = true)
|
155
|
+
find_options = sanitize_without_ordering(searching)
|
156
|
+
unless priority_order.blank?
|
157
|
+
order_parts = [priority_order, find_options[:order]].compact
|
158
|
+
find_options[:order] = order_parts.join(", ")
|
159
|
+
end
|
160
|
+
find_options
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
def order_by_to_order(order_by, order_as, alt_klass = nil)
|
165
|
+
return if order_by.blank?
|
166
|
+
|
167
|
+
k = alt_klass || klass
|
168
|
+
table_name = k.table_name
|
169
|
+
sql_parts = []
|
170
|
+
|
171
|
+
case order_by
|
172
|
+
when Array
|
173
|
+
order_by.each { |part| sql_parts << order_by_to_order(part, order_as, alt_klass) }
|
174
|
+
when Hash
|
175
|
+
raise(ArgumentError, "when passing a hash to order_by you must only have 1 key: {:user_group => :name} not {:user_group => :name, :user_group => :id}. The latter should be [{:user_group => :name}, {:user_group => :id}]") if order_by.keys.size != 1
|
176
|
+
key = order_by.keys.first
|
177
|
+
reflection = k.reflect_on_association(key.to_sym)
|
178
|
+
value = order_by.values.first
|
179
|
+
sql_parts << order_by_to_order(value, order_as, reflection.klass)
|
180
|
+
when Symbol, String
|
181
|
+
part = "#{quote_table_name(table_name)}.#{quote_column_name(order_by)}"
|
182
|
+
part += " #{order_as}" unless order_as.blank?
|
183
|
+
sql_parts << part
|
184
|
+
end
|
185
|
+
|
186
|
+
sql_parts.join(", ")
|
187
|
+
end
|
188
|
+
|
189
|
+
def order_to_order_by(order)
|
190
|
+
# Reversege engineer order, only go 1 level deep with relationships, anything beyond that is probably excessive and not good for performance
|
191
|
+
order_parts = order.split(",").collect do |part|
|
192
|
+
part.strip!
|
193
|
+
part.gsub!(/ (ASC|DESC)$/i, "")
|
194
|
+
part.gsub!(/(.*)\./, "")
|
195
|
+
table_name = ($1 ? $1.gsub(/[^a-z0-9_]/i, "") : nil)
|
196
|
+
part.gsub!(/[^a-z0-9_]/i, "")
|
197
|
+
reflection = nil
|
198
|
+
if table_name && table_name != klass.table_name
|
199
|
+
reflection = klass.reflect_on_association(table_name.to_sym) || klass.reflect_on_association(table_name.singularize.to_sym)
|
200
|
+
next unless reflection
|
201
|
+
{reflection.name.to_s => part}
|
202
|
+
else
|
203
|
+
part
|
204
|
+
end
|
205
|
+
end.compact
|
206
|
+
order_parts.size <= 1 ? order_parts.first : order_parts
|
207
|
+
end
|
208
|
+
|
209
|
+
def build_order_by_auto_joins(order_by_value)
|
210
|
+
case order_by_value
|
211
|
+
when Array
|
212
|
+
order_by_value.collect { |value| build_order_by_auto_joins(value) }.uniq.compact
|
213
|
+
when Hash
|
214
|
+
key = order_by_value.keys.first
|
215
|
+
value = order_by_value.values.first
|
216
|
+
case value
|
217
|
+
when Hash
|
218
|
+
{key.to_sym => build_order_by_auto_joins(value)}
|
219
|
+
else
|
220
|
+
key.to_sym
|
221
|
+
end
|
222
|
+
else
|
223
|
+
nil
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def get_order_by_value(value)
|
228
|
+
Marshal.load(value.unpack("m").first) rescue value
|
229
|
+
end
|
230
|
+
|
231
|
+
def quote_column_name(column_name)
|
232
|
+
klass_connection.quote_column_name(column_name)
|
233
|
+
end
|
234
|
+
|
235
|
+
def quote_table_name(table_name)
|
236
|
+
klass_connection.quote_table_name(table_name)
|
237
|
+
end
|
238
|
+
|
239
|
+
def klass_connection
|
240
|
+
@connection ||= klass.connection
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module Search
|
3
|
+
# = Searchlogic Pagination
|
4
|
+
#
|
5
|
+
# Adds in pagination functionality to searchlogic
|
6
|
+
module Pagination
|
7
|
+
def self.included(klass)
|
8
|
+
klass.class_eval do
|
9
|
+
alias_method_chain :limit=, :pagination
|
10
|
+
alias_method_chain :offset=, :pagination
|
11
|
+
alias_method :per_page, :limit
|
12
|
+
alias_method :per_page=, :limit=
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def limit_with_pagination=(value) # :nodoc:
|
17
|
+
r_value = self.limit_without_pagination = value
|
18
|
+
@page_count = nil
|
19
|
+
if @set_page
|
20
|
+
self.page = (@queued_page || @page) # retry setting page
|
21
|
+
else
|
22
|
+
@page = nil # the memoized page is invalid, so reset it
|
23
|
+
end
|
24
|
+
r_value
|
25
|
+
end
|
26
|
+
|
27
|
+
def offset_with_pagination=(value) #:nodoc
|
28
|
+
r_value = self.offset_without_pagination = value
|
29
|
+
@set_page = @queued_page = @page = nil
|
30
|
+
r_value
|
31
|
+
end
|
32
|
+
|
33
|
+
# The current page that the search is on
|
34
|
+
def page
|
35
|
+
@page ||= (offset.blank? || limit.blank?) ? 1 : (offset.to_f / limit).floor + 1
|
36
|
+
end
|
37
|
+
alias_method :current_page, :page
|
38
|
+
|
39
|
+
# Lets you change the page for the next search
|
40
|
+
def page=(value)
|
41
|
+
@set_page = true
|
42
|
+
|
43
|
+
if value.blank?
|
44
|
+
value = nil
|
45
|
+
@page = value
|
46
|
+
return @offset = @page
|
47
|
+
end
|
48
|
+
|
49
|
+
v = value.to_i
|
50
|
+
|
51
|
+
if limit.blank?
|
52
|
+
@queued_page = v
|
53
|
+
@page = 1
|
54
|
+
@offset = nil
|
55
|
+
else
|
56
|
+
@queued_page = nil
|
57
|
+
@page = v
|
58
|
+
v -= 1 unless v == 0
|
59
|
+
@offset = v * limit
|
60
|
+
end
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
# The total number of pages in your next search
|
65
|
+
def page_count
|
66
|
+
@page_count ||= (per_page.blank? || per_page <= 0) ? 1 : (count / per_page.to_f).ceil
|
67
|
+
end
|
68
|
+
alias_method :page_total, :page_count
|
69
|
+
|
70
|
+
# Always returns 1, this is a convenience method
|
71
|
+
def first_page
|
72
|
+
1
|
73
|
+
end
|
74
|
+
|
75
|
+
# Changes the page to 1 and then runs the "all" search. What's different about this method is that it does not raise an exception if you are on the first page. Unlike prev_page! and next_page!
|
76
|
+
# I don't think an exception raised is warranted, because you are expecting the same results each time it is ran.
|
77
|
+
def first_page!
|
78
|
+
self.page = first_page
|
79
|
+
all
|
80
|
+
end
|
81
|
+
|
82
|
+
# Changes the page to the page - 1
|
83
|
+
def prev_page
|
84
|
+
self.page - 1
|
85
|
+
end
|
86
|
+
|
87
|
+
# Changes the page to page - 1 and runs the "all" search. Be careful with this method because if you are on the first page an exception is raised telling you that you are on the first page.
|
88
|
+
# I thought about just running the first page search again, but that seems confusing and unexpected.
|
89
|
+
def prev_page!
|
90
|
+
raise("You are on the first page") if page == first_page
|
91
|
+
self.page = prev_page
|
92
|
+
all
|
93
|
+
end
|
94
|
+
|
95
|
+
# Change the page to page + 1
|
96
|
+
def next_page
|
97
|
+
self.page + 1
|
98
|
+
end
|
99
|
+
|
100
|
+
# Changes the page to page + 1 and calls the "all" method. Be careful with this method because if you are on the last page an exception is raised telling you that you are on the last page.
|
101
|
+
# I thought about just running the lat page search again, but that seems confusing and unexpected.
|
102
|
+
def next_page!
|
103
|
+
raise("You are on the last page") if page == last_page
|
104
|
+
self.page = next_page
|
105
|
+
all
|
106
|
+
end
|
107
|
+
|
108
|
+
# Always returns the page_count, this is a convenience method
|
109
|
+
def last_page
|
110
|
+
page_count
|
111
|
+
end
|
112
|
+
|
113
|
+
# Changes the page to the last page and runs the "all" search. What's different about this method is that it does not raise an exception if you are on the last page. Unlike prev_page! and next_page!
|
114
|
+
# I don't think an exception raised is warranted, because you are expecting the same results each time it is ran.
|
115
|
+
def last_page!
|
116
|
+
self.page = last_page
|
117
|
+
all
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Searchlogic
|
2
|
+
module Search
|
3
|
+
# = Searchlogic Protection
|
4
|
+
#
|
5
|
+
# This adds protection during mass asignments *only*. This allows you to pass a params object when doing mass assignments and not have to worry about Billy 13 year old adding in SQL injections.
|
6
|
+
# There is a section in the readme that covers protection but to reiterate:
|
7
|
+
#
|
8
|
+
# === Protected
|
9
|
+
#
|
10
|
+
# User.new_search(params[:search])
|
11
|
+
# User.new_conditions(params[:search])
|
12
|
+
#
|
13
|
+
# search.options = params[:search]
|
14
|
+
# conditions.conditions = params[:conditions]
|
15
|
+
#
|
16
|
+
# === NOT Protected
|
17
|
+
#
|
18
|
+
# User.new_search!(params[:search])
|
19
|
+
# User.new_conditions!(params[:search])
|
20
|
+
# User.find(:all, params[:search])
|
21
|
+
# User.first(params[:search])
|
22
|
+
# User.all(params[:search])
|
23
|
+
module Protection
|
24
|
+
# Options that are allowed when protecting against SQL injections (still checked though)
|
25
|
+
SAFE_OPTIONS = Base::SPECIAL_FIND_OPTIONS + [:conditions, :limit, :offset] - [:priority_order]
|
26
|
+
|
27
|
+
VULNERABLE_FIND_OPTIONS = Base::AR_FIND_OPTIONS - SAFE_OPTIONS + [:priority_order]
|
28
|
+
|
29
|
+
VULNERABLE_CALCULATIONS_OPTIONS = Base::AR_CALCULATIONS_OPTIONS - SAFE_OPTIONS + [:priority_order]
|
30
|
+
|
31
|
+
# Options that are not allowed, at all, when protecting against SQL injections
|
32
|
+
VULNERABLE_OPTIONS = Base::OPTIONS - SAFE_OPTIONS
|
33
|
+
|
34
|
+
def self.included(klass)
|
35
|
+
klass.class_eval do
|
36
|
+
attr_reader :protect
|
37
|
+
alias_method_chain :options=, :protection
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def options_with_protection=(values) # :nodoc:
|
42
|
+
return unless values.is_a?(Hash)
|
43
|
+
self.protect = values.delete(:protect) if values.has_key?(:protect) # make sure we do this first
|
44
|
+
frisk!(values) if protect?
|
45
|
+
self.options_without_protection = values
|
46
|
+
end
|
47
|
+
|
48
|
+
# Accepts a boolean. Will protect mass assignemnts if set to true, and unprotect mass assignments if set to false
|
49
|
+
def protect=(value)
|
50
|
+
conditions.protect = value
|
51
|
+
@protect = value
|
52
|
+
end
|
53
|
+
|
54
|
+
# Convenience methof for determing if the search is protected or not.
|
55
|
+
def protect?
|
56
|
+
protect == true
|
57
|
+
end
|
58
|
+
alias_method :protected?, :protect?
|
59
|
+
|
60
|
+
private
|
61
|
+
def order_by_safe?(order_by, alt_klass = nil)
|
62
|
+
return true if order_by.blank?
|
63
|
+
|
64
|
+
k = alt_klass || klass
|
65
|
+
column_names = k.column_names
|
66
|
+
|
67
|
+
[order_by].flatten.each do |column|
|
68
|
+
case column
|
69
|
+
when Hash
|
70
|
+
reflection = k.reflect_on_association(column.keys.first.to_sym)
|
71
|
+
return false unless reflection
|
72
|
+
return false unless order_by_safe?(column.values.first, reflection.klass)
|
73
|
+
when Array
|
74
|
+
return false unless order_by_safe?(column)
|
75
|
+
else
|
76
|
+
return false unless column_names.include?(column.to_s)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def frisk!(options)
|
84
|
+
options.symbolize_keys.fast_assert_valid_keys(SAFE_OPTIONS)
|
85
|
+
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]))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|