searchgasm 0.9.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/Manifest +34 -21
- data/{README.mdown → README.rdoc} +96 -63
- data/Rakefile +1 -1
- data/examples/README.rdoc +4 -0
- data/lib/searchgasm/active_record/associations.rb +40 -42
- data/lib/searchgasm/active_record/base.rb +75 -61
- data/lib/searchgasm/condition/base.rb +127 -0
- data/lib/searchgasm/condition/begins_with.rb +20 -0
- data/lib/searchgasm/condition/child_of.rb +11 -0
- data/lib/searchgasm/condition/contains.rb +20 -0
- data/lib/searchgasm/condition/descendant_of.rb +24 -0
- data/lib/searchgasm/condition/does_not_equal.rb +28 -0
- data/lib/searchgasm/condition/ends_with.rb +20 -0
- data/lib/searchgasm/condition/equals.rb +20 -0
- data/lib/searchgasm/condition/greater_than.rb +25 -0
- data/lib/searchgasm/condition/greater_than_or_equal_to.rb +20 -0
- data/lib/searchgasm/condition/inclusive_descendant_of.rb +13 -0
- data/lib/searchgasm/condition/keywords.rb +33 -0
- data/lib/searchgasm/condition/less_than.rb +25 -0
- data/lib/searchgasm/condition/less_than_or_equal_to.rb +20 -0
- data/lib/searchgasm/condition/sibling_of.rb +16 -0
- data/lib/searchgasm/condition/tree.rb +16 -0
- data/lib/searchgasm/conditions/base.rb +221 -0
- data/lib/searchgasm/conditions/protection.rb +30 -0
- data/lib/searchgasm/config.rb +137 -0
- data/lib/searchgasm/helpers/form_helper.rb +159 -0
- data/lib/searchgasm/helpers/search_helper.rb +178 -0
- data/lib/searchgasm/helpers/utilities_helper.rb +125 -0
- data/lib/searchgasm/search/base.rb +73 -179
- data/lib/searchgasm/search/conditions.rb +42 -166
- data/lib/searchgasm/search/ordering.rb +149 -0
- data/lib/searchgasm/search/pagination.rb +69 -0
- data/lib/searchgasm/search/protection.rb +61 -0
- data/lib/searchgasm/utilities.rb +30 -0
- data/lib/searchgasm/version.rb +44 -47
- data/lib/searchgasm.rb +57 -21
- data/searchgasm.gemspec +71 -46
- data/test/test_active_record_associations.rb +1 -1
- data/test/test_active_record_base.rb +4 -4
- data/test/test_condition.rb +143 -0
- data/test/{test_searchgasm_conditions.rb → test_conditions_base.rb} +43 -33
- data/test/test_search_base.rb +189 -0
- data/test/test_search_ordering.rb +91 -0
- data/test/test_search_pagination.rb +56 -0
- data/test/test_search_protection.rb +35 -0
- metadata +70 -45
- data/lib/searchgasm/search/condition.rb +0 -105
- data/lib/searchgasm/search/condition_types/begins_with_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/child_of_condition.rb +0 -17
- data/lib/searchgasm/search/condition_types/contains_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/descendant_of_condition.rb +0 -30
- data/lib/searchgasm/search/condition_types/does_not_equal_condition.rb +0 -34
- data/lib/searchgasm/search/condition_types/ends_with_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/equals_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/greater_than_condition.rb +0 -31
- data/lib/searchgasm/search/condition_types/greater_than_or_equal_to_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/inclusive_descendant_of_condition.rb +0 -19
- data/lib/searchgasm/search/condition_types/keywords_condition.rb +0 -39
- data/lib/searchgasm/search/condition_types/less_than_condition.rb +0 -31
- data/lib/searchgasm/search/condition_types/less_than_or_equal_to_condition.rb +0 -26
- data/lib/searchgasm/search/condition_types/sibling_of_condition.rb +0 -22
- data/lib/searchgasm/search/condition_types/tree_condition.rb +0 -20
- data/lib/searchgasm/search/utilities.rb +0 -34
- data/test/test_searchgasm_base.rb +0 -185
- data/test/test_searchgasm_condition_types.rb +0 -143
@@ -1,171 +1,47 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def register_condition(klass)
|
11
|
-
raise(ArgumentError, "You can only register conditions that extend BinaryLogic::Searchgasm::Search::ConditionTypes::Condition") unless klass.ancestors.include?(Condition)
|
12
|
-
conditions << klass unless conditions.include?(klass)
|
13
|
-
end
|
14
|
-
|
15
|
-
def conditions
|
16
|
-
@@conditions ||= []
|
17
|
-
end
|
18
|
-
|
19
|
-
def needed?(klass, conditions)
|
20
|
-
if conditions.is_a?(Hash)
|
21
|
-
conditions.stringify_keys.keys.each do |condition|
|
22
|
-
return true unless klass.column_names.include?(condition)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
false
|
27
|
-
end
|
1
|
+
module Searchgasm
|
2
|
+
module Search
|
3
|
+
module Conditions
|
4
|
+
def self.included(klass)
|
5
|
+
klass.class_eval do
|
6
|
+
alias_method_chain :initialize, :conditions
|
7
|
+
alias_method_chain :conditions=, :conditions
|
8
|
+
alias_method_chain :include, :conditions
|
9
|
+
alias_method_chain :sanitize, :conditions
|
28
10
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
args << {:conditions => sanitize}
|
44
|
-
klass.#{method}(*args)
|
45
|
-
end
|
46
|
-
end_eval
|
47
|
-
end
|
48
|
-
|
49
|
-
def assert_valid_values(values)
|
50
|
-
keys = condition_names.collect { |condition_name| condition_name.to_sym }
|
51
|
-
keys += klass.reflect_on_all_associations.collect { |association| association.name }
|
52
|
-
values.symbolize_keys.assert_valid_keys(keys)
|
53
|
-
end
|
54
|
-
|
55
|
-
def associations
|
56
|
-
objects.select { |object| object.is_a?(self.class) }
|
57
|
-
end
|
58
|
-
|
59
|
-
def condition_names
|
60
|
-
@condition_names ||= []
|
61
|
-
end
|
62
|
-
|
63
|
-
def includes
|
64
|
-
i = []
|
65
|
-
associations.each do |association|
|
66
|
-
association_includes = association.includes
|
67
|
-
i << (association_includes.blank? ? association.relationship_name.to_sym : {association.relationship_name.to_sym => association_includes})
|
68
|
-
end
|
69
|
-
i.blank? ? nil : (i.size == 1 ? i.first : i)
|
70
|
-
end
|
71
|
-
|
72
|
-
def objects
|
73
|
-
@objects ||= []
|
74
|
-
end
|
75
|
-
|
76
|
-
def protect?
|
77
|
-
protect == true
|
78
|
-
end
|
79
|
-
|
80
|
-
def sanitize
|
81
|
-
conditions = merge_conditions(*objects.collect { |object| object.sanitize })
|
82
|
-
return scope if conditions.blank?
|
83
|
-
merge_conditions(conditions, scope)
|
84
|
-
end
|
85
|
-
|
86
|
-
def value=(values)
|
87
|
-
case values
|
88
|
-
when Hash
|
89
|
-
assert_valid_values(values)
|
90
|
-
values.each { |condition, value| send("#{condition}=", value) }
|
91
|
-
else
|
92
|
-
raise(ArgumentError, "You can not set a scope or pass SQL while the search is being protected") if protect?
|
93
|
-
self.scope = values
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def value
|
98
|
-
values_hash = {}
|
99
|
-
objects.each do |object|
|
100
|
-
next unless object.explicitly_set_value?
|
101
|
-
values_hash[object.name.to_sym] = object.value
|
102
|
-
end
|
103
|
-
values_hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_with_conditions(klass, init_options = {})
|
14
|
+
self.conditions = Searchgasm::Conditions::Base.new(klass)
|
15
|
+
initialize_without_conditions(klass, init_options)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sets conditions on the search. Accepts a hash or a Searchgasm::Conditions::Base object.
|
19
|
+
def conditions_with_conditions=(values)
|
20
|
+
case values
|
21
|
+
when Searchgasm::Conditions::Base
|
22
|
+
@conditions = values
|
23
|
+
else
|
24
|
+
@conditions.conditions = values
|
104
25
|
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
def add_column_conditions!
|
126
|
-
klass.columns.each do |column|
|
127
|
-
self.class.conditions.each do |condition|
|
128
|
-
name = condition.name_for_column(column)
|
129
|
-
next if name.blank?
|
130
|
-
add_condition!(condition, name, column)
|
131
|
-
condition.aliases_for_column(column).each { |alias_name| add_condition_alias!(alias_name, name) }
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def add_condition!(condition, name, column = nil)
|
137
|
-
self.condition_names << name
|
138
|
-
self.class.class_eval <<-end_eval
|
139
|
-
def #{name}_object
|
140
|
-
if @#{name}.nil?
|
141
|
-
@#{name} = #{condition.name}.new(klass#{column.nil? ? "" : ", \"#{column.name}\""})
|
142
|
-
self.objects << @#{name}
|
143
|
-
end
|
144
|
-
@#{name}
|
145
|
-
end
|
146
|
-
|
147
|
-
def #{name}; #{name}_object.value; end
|
148
|
-
def #{name}=(value); #{name}_object.value = value; end
|
149
|
-
def reset_#{name}!; objects.delete(#{name}_object); @#{name} = nil; end
|
150
|
-
end_eval
|
151
|
-
end
|
152
|
-
|
153
|
-
def add_condition_alias!(alias_name, name)
|
154
|
-
self.condition_names << alias_name
|
155
|
-
self.class.class_eval do
|
156
|
-
alias_method alias_name, name
|
157
|
-
alias_method "#{alias_name}=", "#{name}="
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def add_klass_conditions!
|
162
|
-
self.class.conditions.each do |condition|
|
163
|
-
name = condition.name_for_klass(klass)
|
164
|
-
next if name.blank?
|
165
|
-
add_condition!(condition, name)
|
166
|
-
condition.aliases_for_klass(klass).each { |alias_name| add_condition_alias!(alias_name, name) }
|
167
|
-
end
|
168
|
-
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def include_with_conditions
|
29
|
+
includes = [include_without_conditions, conditions.includes].flatten.compact.uniq
|
30
|
+
includes.blank? ? nil : (includes.size == 1 ? includes.first : includes)
|
31
|
+
end
|
32
|
+
|
33
|
+
def sanitize_with_conditions(for_method = nil)
|
34
|
+
find_options = sanitize_without_conditions(for_method)
|
35
|
+
find_options[:conditions] = find_options[:conditions].sanitize if find_options[:conditions]
|
36
|
+
find_options
|
37
|
+
end
|
38
|
+
|
39
|
+
def scope
|
40
|
+
conditions.scope
|
41
|
+
end
|
42
|
+
|
43
|
+
def scope=(value)
|
44
|
+
conditions.scope = value
|
169
45
|
end
|
170
46
|
end
|
171
47
|
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Searchgasm
|
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 Searchgasm::Helpers::SearchHelper for more information.
|
7
|
+
module Ordering
|
8
|
+
def self.included(klass)
|
9
|
+
klass.class_eval do
|
10
|
+
alias_method_chain :include, :ordering
|
11
|
+
alias_method_chain :order=, :ordering
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def include_with_ordering
|
16
|
+
includes = [include_without_ordering, order_by_includes].flatten.compact.uniq
|
17
|
+
includes.blank? ? nil : (includes.size == 1 ? includes.first : includes)
|
18
|
+
end
|
19
|
+
|
20
|
+
def order_with_ordering=(value)
|
21
|
+
@order_by = nil
|
22
|
+
self.order_without_ordering = value
|
23
|
+
end
|
24
|
+
|
25
|
+
# Convenience method for determining if the ordering is ascending
|
26
|
+
def asc?
|
27
|
+
!desc?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convenience method for determining if the ordering is descending
|
31
|
+
def desc?
|
32
|
+
order_as == "DESC"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determines how the search is being ordered: as DESC or ASC
|
36
|
+
def order_as
|
37
|
+
return "ASC" if order.blank?
|
38
|
+
order =~ /ASC$/i ? "ASC" : "DESC"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sets how the results will be ordered: ASC or DESC
|
42
|
+
def order_as=(value)
|
43
|
+
value = value.to_s.upcase
|
44
|
+
raise(ArgumentError, "order_as only accepts a string as ASC or DESC") unless ["ASC", "DESC"].include?(value)
|
45
|
+
|
46
|
+
if order.blank?
|
47
|
+
self.order = order_by_to_order(order_by, value)
|
48
|
+
else
|
49
|
+
self.order.gsub!(/(ASC|DESC)/i, value)
|
50
|
+
end
|
51
|
+
|
52
|
+
value
|
53
|
+
end
|
54
|
+
|
55
|
+
# Determines by what columns the search is being ordered. This is nifty in that is reverse engineers the order SQL to determine this, only
|
56
|
+
# if you haven't explicitly set the order_by option yourself.
|
57
|
+
def order_by
|
58
|
+
return @order_by if @order_by
|
59
|
+
|
60
|
+
if !order.blank?
|
61
|
+
# Reversege engineer order, only go 1 level deep with relationships, anything beyond that is probably excessive and not good for performance
|
62
|
+
order_parts = order.split(",").collect do |part|
|
63
|
+
part.strip!
|
64
|
+
part.gsub!(/ (ASC|DESC)$/i, "").gsub!(/(.*)\./, "")
|
65
|
+
table_name = ($1 ? $1.gsub(/[^a-z0-9_]/i, "") : nil)
|
66
|
+
part.gsub!(/[^a-z0-9_]/i, "")
|
67
|
+
reflection = nil
|
68
|
+
if table_name && table_name != klass.table_name
|
69
|
+
reflection = klass.reflect_on_association(table_name.to_sym) || klass.reflect_on_association(table_name.singularize.to_sym)
|
70
|
+
next unless reflection
|
71
|
+
{reflection.name.to_s => part}
|
72
|
+
else
|
73
|
+
part
|
74
|
+
end
|
75
|
+
end.compact
|
76
|
+
order_parts.size <= 1 ? order_parts.first : order_parts
|
77
|
+
else
|
78
|
+
klass.primary_key
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Lets you set how to order the data
|
83
|
+
#
|
84
|
+
# === Examples
|
85
|
+
#
|
86
|
+
# In these examples "ASC" is determined by the value of order_as
|
87
|
+
#
|
88
|
+
# order_by = :id # => users.id ASC
|
89
|
+
# order_by = [:id, name] # => users.id ASC, user.name ASC
|
90
|
+
# order_by = [:id, {:user_group => :name}] # => users.id ASC, user_groups.name ASC
|
91
|
+
def order_by=(value)
|
92
|
+
@order_by = get_order_by_value(value)
|
93
|
+
@order = order_by_to_order(@order_by, order_as) # use @order so @order_by doesnt get reset
|
94
|
+
@order_by
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns the includes neccessary for the "order" statement so that we don't get an SQL error
|
98
|
+
def order_by_includes
|
99
|
+
@order_by_includes ||= []
|
100
|
+
@order_by_includes.compact!
|
101
|
+
@order_by_includes.uniq!
|
102
|
+
@order_by_includes
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def order_by_to_order(order_by, order_as, alt_klass = nil, new_includes = [])
|
107
|
+
table_name = (alt_klass || klass).table_name
|
108
|
+
sql_parts = []
|
109
|
+
|
110
|
+
case order_by
|
111
|
+
when Array
|
112
|
+
order_by.each { |part| sql_parts << order_by_to_order(part, order_as) }
|
113
|
+
when Hash
|
114
|
+
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
|
115
|
+
k = order_by.keys.first
|
116
|
+
v = order_by.values.first
|
117
|
+
new_includes << k.to_sym
|
118
|
+
sql_parts << order_by_to_order(v, order_as, k.to_s.classify.constantize, new_includes)
|
119
|
+
when Symbol, String
|
120
|
+
new_include = build_order_by_includes(new_includes)
|
121
|
+
self.order_by_includes << new_include if new_include
|
122
|
+
sql_parts << "#{quote_table_name(table_name)}.#{quote_column_name(order_by)} #{order_as}"
|
123
|
+
end
|
124
|
+
|
125
|
+
sql_parts.join(", ")
|
126
|
+
end
|
127
|
+
|
128
|
+
def build_order_by_includes(includes)
|
129
|
+
return includes.first if includes.size <= 1
|
130
|
+
includes = includes.dup
|
131
|
+
|
132
|
+
key = includes.shift
|
133
|
+
{key => build_order_by_includes(includes)}
|
134
|
+
end
|
135
|
+
|
136
|
+
def get_order_by_value(value)
|
137
|
+
Marshal.load(value.unpack("m").first) rescue value
|
138
|
+
end
|
139
|
+
|
140
|
+
def quote_column_name(column_name)
|
141
|
+
klass.connection.quote_column_name(column_name)
|
142
|
+
end
|
143
|
+
|
144
|
+
def quote_table_name(table_name)
|
145
|
+
klass.connection.quote_table_name(table_name)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Searchgasm
|
2
|
+
module Search
|
3
|
+
module Pagination
|
4
|
+
def self.included(klass)
|
5
|
+
klass.class_eval do
|
6
|
+
alias_method_chain :limit=, :pagination
|
7
|
+
alias_method_chain :offset=, :pagination
|
8
|
+
alias_method :per_page, :limit
|
9
|
+
alias_method :per_page=, :limit=
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def limit_with_pagination=(value)
|
14
|
+
self.limit_without_pagination = value
|
15
|
+
self.page = @page unless @page.nil? # retry page now that the limit has changed
|
16
|
+
limit
|
17
|
+
end
|
18
|
+
|
19
|
+
def offset_with_pagination=(value)
|
20
|
+
self.offset_without_pagination = value
|
21
|
+
@page = nil
|
22
|
+
offset
|
23
|
+
end
|
24
|
+
|
25
|
+
def page
|
26
|
+
return 1 if offset.blank? || limit.blank?
|
27
|
+
(offset.to_f / limit).floor + 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def page=(value)
|
31
|
+
# Have to use @offset, since self.offset= resets @page
|
32
|
+
if value.nil?
|
33
|
+
@page = value
|
34
|
+
return @offset = value
|
35
|
+
end
|
36
|
+
|
37
|
+
v = value.to_i
|
38
|
+
@page = v
|
39
|
+
|
40
|
+
if limit.blank?
|
41
|
+
@offset = nil
|
42
|
+
else
|
43
|
+
v -= 1 unless v == 0
|
44
|
+
@offset = v * limit
|
45
|
+
end
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
def page_count
|
50
|
+
return 1 if per_page.blank? || per_page <= 0
|
51
|
+
# Letting AR caching kick in with the count query
|
52
|
+
(count / per_page.to_f).ceil
|
53
|
+
end
|
54
|
+
alias_method :page_total, :page_count
|
55
|
+
|
56
|
+
def next_page!
|
57
|
+
raise("You are on the last page") if page == page_count
|
58
|
+
self.page += 1
|
59
|
+
all
|
60
|
+
end
|
61
|
+
|
62
|
+
def prev_page!
|
63
|
+
raise("You are on the first page") if page == 1
|
64
|
+
self.page -= 1
|
65
|
+
all
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Searchgasm
|
2
|
+
module Search
|
3
|
+
module Protection
|
4
|
+
# Options that are allowed when protecting against SQL injections (still checked though)
|
5
|
+
SAFE_OPTIONS = Base::SPECIAL_FIND_OPTIONS + [:conditions, :limit, :offset]
|
6
|
+
|
7
|
+
# Options that are not allowed, at all, when protecting against SQL injections
|
8
|
+
VULNERABLE_OPTIONS = Base::VALID_FIND_OPTIONS - SAFE_OPTIONS
|
9
|
+
|
10
|
+
def self.included(klass)
|
11
|
+
klass.class_eval do
|
12
|
+
attr_reader :protect
|
13
|
+
alias_method_chain :options=, :protection
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def options_with_protection=(values)
|
18
|
+
return unless values.is_a?(Hash)
|
19
|
+
self.protect = values.delete(:protect) if values.has_key?(:protect) # make sure we do this first
|
20
|
+
frisk!(values) if protect?
|
21
|
+
self.options_without_protection = values
|
22
|
+
end
|
23
|
+
|
24
|
+
def protect=(value)
|
25
|
+
conditions.protect = value
|
26
|
+
@protect = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def protect?
|
30
|
+
protect == true
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def order_by_safe?(order_by, alt_klass = nil)
|
35
|
+
return true if order_by.blank?
|
36
|
+
|
37
|
+
k = alt_klass || klass
|
38
|
+
column_names = k.column_names
|
39
|
+
|
40
|
+
[order_by].flatten.each do |column|
|
41
|
+
case column
|
42
|
+
when Hash
|
43
|
+
return false unless k.reflect_on_association(column.keys.first.to_sym)
|
44
|
+
return false unless order_by_safe?(column.values.first, column.keys.first.to_s.classify.constantize)
|
45
|
+
when Array
|
46
|
+
return false unless order_by_safe?(column)
|
47
|
+
else
|
48
|
+
return false unless column_names.include?(column.to_s)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def frisk!(options)
|
56
|
+
options.symbolize_keys.assert_valid_keys(SAFE_OPTIONS)
|
57
|
+
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]))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Searchgasm
|
2
|
+
module Utilities # :nodoc:
|
3
|
+
private
|
4
|
+
def merge_conditions(*conditions)
|
5
|
+
options = conditions.extract_options!
|
6
|
+
conditions.delete_if { |condition| condition.blank? }
|
7
|
+
return if conditions.blank?
|
8
|
+
return conditions.first if conditions.size == 1
|
9
|
+
|
10
|
+
conditions_strs = []
|
11
|
+
conditions_subs = []
|
12
|
+
|
13
|
+
conditions.each do |condition|
|
14
|
+
next if condition.blank?
|
15
|
+
arr_condition = [condition].flatten
|
16
|
+
conditions_strs << arr_condition.first
|
17
|
+
conditions_subs += arr_condition[1..-1]
|
18
|
+
end
|
19
|
+
|
20
|
+
return if conditions_strs.blank?
|
21
|
+
|
22
|
+
join = options[:any] ? "OR" : "AND"
|
23
|
+
conditions_str = "(#{conditions_strs.join(") #{join} (")})"
|
24
|
+
|
25
|
+
return conditions_str if conditions_subs.blank?
|
26
|
+
|
27
|
+
[conditions_str, *conditions_subs]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/searchgasm/version.rb
CHANGED
@@ -21,61 +21,58 @@
|
|
21
21
|
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
22
22
|
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
23
23
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
-
module
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
class Version
|
32
|
-
|
33
|
-
include Comparable
|
24
|
+
module Searchgasm
|
25
|
+
# = Version
|
26
|
+
#
|
27
|
+
# A class for describing the current version of a library. The version
|
28
|
+
# consists of three parts: the +major+ number, the +minor+ number, and the
|
29
|
+
# +tiny+ (or +patch+) number.
|
30
|
+
class Version
|
34
31
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
include Comparable
|
33
|
+
|
34
|
+
# A convenience method for instantiating a new Version instance with the
|
35
|
+
# given +major+, +minor+, and +tiny+ components.
|
36
|
+
def self.[](major, minor, tiny)
|
37
|
+
new(major, minor, tiny)
|
38
|
+
end
|
40
39
|
|
41
|
-
|
40
|
+
attr_reader :major, :minor, :tiny
|
42
41
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
42
|
+
# Create a new Version object with the given components.
|
43
|
+
def initialize(major, minor, tiny)
|
44
|
+
@major, @minor, @tiny = major, minor, tiny
|
45
|
+
end
|
47
46
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
# Compare this version to the given +version+ object.
|
48
|
+
def <=>(version)
|
49
|
+
to_i <=> version.to_i
|
50
|
+
end
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
52
|
+
# Converts this version object to a string, where each of the three
|
53
|
+
# version components are joined by the '.' character. E.g., 2.0.0.
|
54
|
+
def to_s
|
55
|
+
@to_s ||= [@major, @minor, @tiny].join(".")
|
56
|
+
end
|
58
57
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
58
|
+
# Converts this version to a canonical integer that may be compared
|
59
|
+
# against other version objects.
|
60
|
+
def to_i
|
61
|
+
@to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_a
|
65
|
+
[@major, @minor, @tiny]
|
66
|
+
end
|
68
67
|
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
MAJOR = 0
|
69
|
+
MINOR = 9
|
70
|
+
TINY = 7
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
72
|
+
# The current version as a Version instance
|
73
|
+
CURRENT = new(MAJOR, MINOR, TINY)
|
74
|
+
# The current version as a String
|
75
|
+
STRING = CURRENT.to_s
|
79
76
|
|
80
77
|
end
|
81
78
|
|