searchgasm 0.9.0
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/CHANGELOG +1 -0
- data/MIT-LICENSE +20 -0
- data/Manifest +28 -0
- data/README.mdown +240 -0
- data/Rakefile +17 -0
- data/init.rb +1 -0
- data/lib/searchgasm/active_record/associations.rb +56 -0
- data/lib/searchgasm/active_record/base.rb +70 -0
- data/lib/searchgasm/active_record/protection.rb +37 -0
- data/lib/searchgasm/helpers.rb +100 -0
- data/lib/searchgasm/search/base.rb +142 -0
- data/lib/searchgasm/search/condition.rb +168 -0
- data/lib/searchgasm/search/conditions.rb +154 -0
- data/lib/searchgasm/search/utilities.rb +34 -0
- data/lib/searchgasm/version.rb +82 -0
- data/lib/searchgasm.rb +9 -0
- data/searchgasm.gemspec +124 -0
- data/test/fixtures/accounts.yml +15 -0
- data/test/fixtures/orders.yml +14 -0
- data/test/fixtures/users.yml +27 -0
- data/test/libs/acts_as_tree.rb +98 -0
- data/test/libs/rexml_fix.rb +14 -0
- data/test/test_active_record_associations.rb +38 -0
- data/test/test_active_record_base.rb +48 -0
- data/test/test_active_record_protection.rb +0 -0
- data/test/test_helper.rb +78 -0
- data/test/test_searchgasm_base.rb +116 -0
- data/test/test_searchgasm_condition.rb +149 -0
- data/test/test_searchgasm_conditions.rb +137 -0
- metadata +122 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
module BinaryLogic
|
2
|
+
module Searchgasm
|
3
|
+
module Search
|
4
|
+
class Base
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
SPECIAL_FIND_OPTIONS = [:order_by, :order_as, :page, :per_page]
|
8
|
+
VALID_FIND_OPTIONS = ::ActiveRecord::Base.valid_find_options + SPECIAL_FIND_OPTIONS
|
9
|
+
|
10
|
+
attr_accessor :klass
|
11
|
+
attr_reader :conditions
|
12
|
+
attr_writer :options
|
13
|
+
|
14
|
+
def initialize(klass, options = {})
|
15
|
+
self.klass = klass
|
16
|
+
self.conditions = Conditions.new(klass)
|
17
|
+
self.options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
(::ActiveRecord::Base.valid_find_options - [:conditions]).each do |option|
|
21
|
+
class_eval <<-SRC
|
22
|
+
def #{option}(sanitize = false); options[:#{option}]; end
|
23
|
+
def #{option}=(value); self.options[:#{option}] = value; end
|
24
|
+
SRC
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :per_page, :limit
|
28
|
+
|
29
|
+
def all
|
30
|
+
klass.all(sanitize)
|
31
|
+
end
|
32
|
+
alias_method :search, :all
|
33
|
+
|
34
|
+
def conditions=(value)
|
35
|
+
case value
|
36
|
+
when Conditions
|
37
|
+
@conditions = value
|
38
|
+
else
|
39
|
+
@conditions.value = value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def conditions(sanitize = false)
|
44
|
+
sanitize ? @conditions.sanitize : @conditions
|
45
|
+
end
|
46
|
+
|
47
|
+
def find(target)
|
48
|
+
case target
|
49
|
+
when :all then all
|
50
|
+
when :first then first
|
51
|
+
else raise(ArgumentError, "The argument must be :all or :first")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def first
|
56
|
+
klass.first(sanitize)
|
57
|
+
end
|
58
|
+
|
59
|
+
def include(sanitize = false)
|
60
|
+
includes = [self.options[:include], conditions.includes].flatten.compact
|
61
|
+
includes.blank? ? nil : (includes.size == 1 ? includes.first : includes)
|
62
|
+
end
|
63
|
+
|
64
|
+
def limit=(value)
|
65
|
+
return options[:limit] = nil if value.nil? || value == 0
|
66
|
+
|
67
|
+
old_limit = options[:limit]
|
68
|
+
options[:limit] = value
|
69
|
+
self.page = @page if !@page.blank? # retry page now that limit is set
|
70
|
+
value
|
71
|
+
end
|
72
|
+
alias_method :per_page=, :limit=
|
73
|
+
|
74
|
+
def options
|
75
|
+
@options ||= {}
|
76
|
+
end
|
77
|
+
|
78
|
+
def options=(options)
|
79
|
+
return unless options.is_a?(Hash)
|
80
|
+
options.each { |option, value| send("#{option}=", value) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def order_as
|
84
|
+
return "DESC" if order.blank?
|
85
|
+
order =~ /ASC$/i ? "ASC" : "DESC"
|
86
|
+
end
|
87
|
+
|
88
|
+
def order_as=(value)
|
89
|
+
# reset order
|
90
|
+
end
|
91
|
+
|
92
|
+
def order_by
|
93
|
+
# need to return a cached value of order_by, not smart to figure it out from order
|
94
|
+
end
|
95
|
+
|
96
|
+
def order_by=(value)
|
97
|
+
# do your magic here and set order approperiately
|
98
|
+
end
|
99
|
+
|
100
|
+
def page
|
101
|
+
return 1 if offset.blank?
|
102
|
+
(offset.to_f / limit).ceil
|
103
|
+
end
|
104
|
+
|
105
|
+
def page=(value)
|
106
|
+
return self.offset = nil if value.nil?
|
107
|
+
|
108
|
+
if limit.blank?
|
109
|
+
@page = value
|
110
|
+
else
|
111
|
+
@page = nil
|
112
|
+
self.offset = value * limit
|
113
|
+
end
|
114
|
+
value
|
115
|
+
end
|
116
|
+
|
117
|
+
def reset!
|
118
|
+
conditions.reset!
|
119
|
+
self.options = {}
|
120
|
+
end
|
121
|
+
|
122
|
+
def sanitize
|
123
|
+
find_options = {}
|
124
|
+
::ActiveRecord::Base.valid_find_options.each do |find_option|
|
125
|
+
value = send(find_option, true)
|
126
|
+
next if value.blank?
|
127
|
+
find_options[find_option] = value
|
128
|
+
end
|
129
|
+
find_options
|
130
|
+
end
|
131
|
+
|
132
|
+
def scope
|
133
|
+
conditions.scope
|
134
|
+
end
|
135
|
+
|
136
|
+
def scope=(value)
|
137
|
+
conditions.scope = value
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module BinaryLogic
|
2
|
+
module Searchgasm
|
3
|
+
module Search
|
4
|
+
class Condition
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
BLACKLISTED_WORDS = ('a'..'z').to_a + ["about", "an", "are", "as", "at", "be", "by", "com", "de", "en", "for", "from", "how", "in", "is", "it", "la", "of", "on", "or", "that", "the", "the", "this", "to", "und", "was", "what", "when", "where", "who", "will", "with", "www"] # from ranks.nl
|
8
|
+
attr_accessor :column, :condition, :name, :klass
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def generate_name(column, condition)
|
13
|
+
name_parts = []
|
14
|
+
name_parts << (column.is_a?(String) ? column : column.name) unless column.blank?
|
15
|
+
name_parts << condition unless condition.blank?
|
16
|
+
name_parts.join("_")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(condition, klass, column = nil)
|
21
|
+
raise(ArgumentError, "#{klass.name} must acts_as_tree to use the :#{condition} condition") if requires_tree?(condition) && !has_tree?(klass)
|
22
|
+
|
23
|
+
self.condition = condition
|
24
|
+
self.name = self.class.generate_name(column, condition)
|
25
|
+
self.klass = klass
|
26
|
+
self.column = column.is_a?(String) ? klass.columns_hash[column] : column
|
27
|
+
end
|
28
|
+
|
29
|
+
def explicitly_set_value=(value)
|
30
|
+
@explicitly_set_value = value
|
31
|
+
end
|
32
|
+
|
33
|
+
# Need this if someone wants to actually use nil in a meaningful way
|
34
|
+
def explicitly_set_value?
|
35
|
+
@explicitly_set_value == true
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_tree?(klass = klass)
|
39
|
+
!klass.reflect_on_association(:parent).nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def ignore_blanks?
|
43
|
+
![:equals, :does_not_equal].include?(condition)
|
44
|
+
end
|
45
|
+
|
46
|
+
def quote_column_name(column_name)
|
47
|
+
klass.connection.quote_column_name(column_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
def quoted_column_name
|
51
|
+
quote_column_name(column.name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def requires_tree?(condition = condition)
|
55
|
+
[:child_of, :sibling_of, :descendent_of, :inclusive_descendent_of].include?(condition)
|
56
|
+
end
|
57
|
+
|
58
|
+
def sanitize
|
59
|
+
return unless explicitly_set_value?
|
60
|
+
v = value
|
61
|
+
v = v.utc if false && [:time, :timestamp, :datetime].include?(column.type) && klass.time_zone_aware_attributes && !klass.skip_time_zone_conversion_for_attributes.include?(column.name.to_sym)
|
62
|
+
generate_conditions(condition, v)
|
63
|
+
end
|
64
|
+
|
65
|
+
def table_name
|
66
|
+
klass.connection.quote_table_name(klass.table_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def value
|
70
|
+
@value.is_a?(String) ? column.type_cast(@value) : @value
|
71
|
+
end
|
72
|
+
|
73
|
+
def value=(v)
|
74
|
+
return if ignore_blanks? && v.blank?
|
75
|
+
self.explicitly_set_value = true
|
76
|
+
@value = v
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def generate_conditions(condition, value)
|
81
|
+
if [:equals, :does_not_equal].include?(condition)
|
82
|
+
# Let ActiveRecord handle this
|
83
|
+
sql = klass.send(:sanitize_sql_hash_for_conditions, {column.name => value})
|
84
|
+
if condition == :does_not_equal
|
85
|
+
sql.gsub!(/ IS /, " IS NOT ")
|
86
|
+
sql.gsub!(/ BETWEEN /, " NOT BETWEEN ")
|
87
|
+
sql.gsub!(/ IN /, " NOT IN ")
|
88
|
+
sql.gsub!(/=/, "!=")
|
89
|
+
end
|
90
|
+
return [sql]
|
91
|
+
end
|
92
|
+
|
93
|
+
strs = []
|
94
|
+
subs = []
|
95
|
+
|
96
|
+
if value.is_a?(Array)
|
97
|
+
merge_conditions(*value.collect { |v| generate_conditions(condition, v) })
|
98
|
+
else
|
99
|
+
case condition
|
100
|
+
when :begins_with
|
101
|
+
search_parts = value.split(/ /)
|
102
|
+
search_parts.each do |search_part|
|
103
|
+
strs << "#{table_name}.#{quoted_column_name} LIKE ?"
|
104
|
+
subs << "#{search_part}%"
|
105
|
+
end
|
106
|
+
when :contains
|
107
|
+
strs << "#{table_name}.#{quoted_column_name} LIKE ?"
|
108
|
+
subs << "%#{value}%"
|
109
|
+
when :ends_with
|
110
|
+
search_parts = value.split(/ /)
|
111
|
+
search_parts.each do |search_part|
|
112
|
+
strs << "#{table_name}.#{quoted_column_name} LIKE ?"
|
113
|
+
subs << "%#{search_part}"
|
114
|
+
end
|
115
|
+
when :greater_than
|
116
|
+
strs << "#{table_name}.#{quoted_column_name} > ?"
|
117
|
+
subs << value
|
118
|
+
when :greater_than_or_equal_to
|
119
|
+
strs << "#{table_name}.#{quoted_column_name} >= ?"
|
120
|
+
subs << value
|
121
|
+
when :keywords
|
122
|
+
search_parts = value.gsub(/,/, " ").split(/ /).collect { |word| word.downcase.gsub(/[^[:alnum:]]/, ''); }.uniq.select { |word| !BLACKLISTED_WORDS.include?(word.downcase) && !word.blank? }
|
123
|
+
search_parts.each do |search_part|
|
124
|
+
strs << "#{table_name}.#{quoted_column_name} LIKE ?"
|
125
|
+
subs << "%#{search_part}%"
|
126
|
+
end
|
127
|
+
when :less_than
|
128
|
+
strs << "#{table_name}.#{quoted_column_name} < ?"
|
129
|
+
subs << value
|
130
|
+
when :less_than_or_equal_to
|
131
|
+
strs << "#{table_name}.#{quoted_column_name} <= ?"
|
132
|
+
subs << value
|
133
|
+
when :child_of
|
134
|
+
parent_association = klass.reflect_on_association(:parent)
|
135
|
+
foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
|
136
|
+
strs << "#{table_name}.#{quote_column_name(foreign_key_name)} = ?"
|
137
|
+
subs << value
|
138
|
+
when :sibling_of
|
139
|
+
parent_association = klass.reflect_on_association(:parent)
|
140
|
+
foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
|
141
|
+
parent_id = klass.find(value).send(foreign_key_name)
|
142
|
+
return generate_conditions(:child_of, parent_id)
|
143
|
+
when :descendent_of
|
144
|
+
# Wish I knew how to do this in SQL
|
145
|
+
root = klass.find(value) rescue return
|
146
|
+
condition_strs = []
|
147
|
+
all_children_ids(root).each do |child_id|
|
148
|
+
condition_strs << "#{table_name}.#{quote_column_name(klass.primary_key)} = ?"
|
149
|
+
subs << child_id
|
150
|
+
end
|
151
|
+
strs << condition_strs.join(" OR ")
|
152
|
+
when :inclusive_descendent_of
|
153
|
+
return merge_conditions(["#{table_name}.#{quote_column_name(klass.primary_key)} = ?", value], generate_conditions(:descendent_of, value), :any => true)
|
154
|
+
end
|
155
|
+
|
156
|
+
[strs.join(" AND "), *subs]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def all_children_ids(record)
|
161
|
+
ids = record.children.collect { |child| child.send(klass.primary_key) }
|
162
|
+
record.children.each { |child| ids += all_children_ids(child) }
|
163
|
+
ids
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module BinaryLogic
|
2
|
+
module Searchgasm
|
3
|
+
module Search
|
4
|
+
class Conditions
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
attr_accessor :klass, :relationship_name, :scope
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def conditions_for_column_type(type)
|
11
|
+
condition_names = [:equals, :does_not_equal]
|
12
|
+
case type
|
13
|
+
when :string, :text
|
14
|
+
condition_names += [:begins_with, :contains, :keywords, :ends_with]
|
15
|
+
when :integer, :float, :decimal, :datetime, :timestamp, :time, :date
|
16
|
+
condition_names += [:greater_than, :greater_than_or_equal_to, :less_than, :less_than_or_equal_to]
|
17
|
+
end
|
18
|
+
condition_names
|
19
|
+
end
|
20
|
+
|
21
|
+
def alias_conditions(condition)
|
22
|
+
case condition
|
23
|
+
when :equals then ["", :is]
|
24
|
+
when :does_not_equal then [:is_not, :not]
|
25
|
+
when :begins_with then [:starts_with]
|
26
|
+
when :contains then [:like]
|
27
|
+
when :greater_than then [:gt, :after]
|
28
|
+
when :greater_than_or_equal_to then [:at_least, :gte]
|
29
|
+
when :less_than then [:lt, :before]
|
30
|
+
when :less_than_or_equal_to then [:at_most, :lte]
|
31
|
+
else []
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(klass, values = {})
|
37
|
+
self.klass = klass
|
38
|
+
klass.columns.each { |column| add_conditions_for_column!(column) }
|
39
|
+
klass.reflect_on_all_associations.each { |association| add_association!(association) }
|
40
|
+
self.value = values
|
41
|
+
end
|
42
|
+
|
43
|
+
def associations
|
44
|
+
objects.select { |object| object.is_a?(self.class) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def includes
|
48
|
+
i = []
|
49
|
+
associations.each do |association|
|
50
|
+
association_includes = association.includes
|
51
|
+
i << (association_includes.blank? ? association.relationship_name.to_sym : {association.relationship_name.to_sym => association_includes})
|
52
|
+
end
|
53
|
+
i.blank? ? nil : (i.size == 1 ? i.first : i)
|
54
|
+
end
|
55
|
+
|
56
|
+
def objects
|
57
|
+
@objects ||= []
|
58
|
+
end
|
59
|
+
|
60
|
+
def reset!
|
61
|
+
dupped_objects = objects.dup
|
62
|
+
dupped_objects.each do |object|
|
63
|
+
if object.is_a?(self.class)
|
64
|
+
send("reset_#{object.relationship_name}!")
|
65
|
+
else
|
66
|
+
send("reset_#{object.name}!")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
objects
|
70
|
+
end
|
71
|
+
|
72
|
+
def sanitize
|
73
|
+
sanitized_objects = merge_conditions(*objects.collect { |object| object.sanitize })
|
74
|
+
return scope if sanitized_objects.blank?
|
75
|
+
merge_conditions(sanitized_objects, scope)
|
76
|
+
end
|
77
|
+
|
78
|
+
def value=(conditions)
|
79
|
+
reset!
|
80
|
+
self.scope = nil
|
81
|
+
|
82
|
+
case conditions
|
83
|
+
when Hash
|
84
|
+
conditions.each { |condition, value| send("#{condition}=", value) }
|
85
|
+
else
|
86
|
+
self.scope = conditions
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def value
|
91
|
+
values_hash = {}
|
92
|
+
objects.each do |object|
|
93
|
+
next unless object.explicitly_set_value?
|
94
|
+
values_hash[object.name.to_sym] = object.value
|
95
|
+
end
|
96
|
+
values_hash
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def add_association!(association)
|
101
|
+
self.class.class_eval <<-SRC
|
102
|
+
def #{association.name}
|
103
|
+
if @#{association.name}.nil?
|
104
|
+
@#{association.name} = self.class.new(#{association.class_name})
|
105
|
+
@#{association.name}.relationship_name = "#{association.name}"
|
106
|
+
self.objects << @#{association.name}
|
107
|
+
end
|
108
|
+
@#{association.name}
|
109
|
+
end
|
110
|
+
|
111
|
+
def #{association.name}=(value); #{association.name}.value = value; end
|
112
|
+
def reset_#{association.name}!; objects.delete(#{association.name}); @#{association.name} = nil; end
|
113
|
+
SRC
|
114
|
+
|
115
|
+
association.name
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_conditions_for_column!(column)
|
119
|
+
self.class.conditions_for_column_type(column.type).collect { |condition_name| add_condition!(condition_name, column) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def add_condition!(condition_name, column)
|
123
|
+
name = Condition.generate_name(column, condition_name)
|
124
|
+
|
125
|
+
# Define accessor methods
|
126
|
+
self.class.class_eval <<-SRC
|
127
|
+
def #{name}_object
|
128
|
+
if @#{name}.nil?
|
129
|
+
@#{name} = Condition.new(:#{condition_name}, klass, "#{column.name}")
|
130
|
+
self.objects << @#{name}
|
131
|
+
end
|
132
|
+
@#{name}
|
133
|
+
end
|
134
|
+
|
135
|
+
def #{name}; #{name}_object.value; end
|
136
|
+
def #{name}=(value); #{name}_object.value = value; end
|
137
|
+
def reset_#{name}!; objects.delete(#{name}_object); @#{name} = nil; end
|
138
|
+
SRC
|
139
|
+
|
140
|
+
# Define aliases
|
141
|
+
self.class.alias_conditions(condition_name).each do |alias_condition_name|
|
142
|
+
alias_name = Condition.generate_name(column, alias_condition_name)
|
143
|
+
self.class.class_eval do
|
144
|
+
alias_method alias_name, name
|
145
|
+
alias_method "#{alias_name}=", "#{name}="
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
name
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module BinaryLogic
|
2
|
+
module Searchgasm
|
3
|
+
module Search
|
4
|
+
module Utilities
|
5
|
+
private
|
6
|
+
def merge_conditions(*conditions)
|
7
|
+
options = conditions.extract_options!
|
8
|
+
conditions.delete_if { |condition| condition.blank? }
|
9
|
+
return if conditions.blank?
|
10
|
+
return conditions.first if conditions.size == 1
|
11
|
+
|
12
|
+
conditions_strs = []
|
13
|
+
conditions_subs = []
|
14
|
+
|
15
|
+
conditions.each do |condition|
|
16
|
+
next if condition.blank?
|
17
|
+
arr_condition = [condition].flatten
|
18
|
+
conditions_strs << arr_condition.first
|
19
|
+
conditions_subs += arr_condition[1..-1]
|
20
|
+
end
|
21
|
+
|
22
|
+
return if conditions_strs.blank?
|
23
|
+
|
24
|
+
join = options[:any] ? "OR" : "AND"
|
25
|
+
conditions_str = "(#{conditions_strs.join(") #{join} (")})"
|
26
|
+
|
27
|
+
return conditions_str if conditions_subs.blank?
|
28
|
+
|
29
|
+
[conditions_str, *conditions_subs]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# (The MIT License)
|
2
|
+
#
|
3
|
+
# Copyright (c) 2008 Jamis Buck <jamis@37signals.com>,
|
4
|
+
# with modifications by Bruce Williams <bruce@fiveruns.com>
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
# a copy of this software and associated documentation files (the
|
8
|
+
# 'Software'), to deal in the Software without restriction, including
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
# the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be
|
15
|
+
# included in all copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
20
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
21
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
22
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
23
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
24
|
+
module BinaryLogic
|
25
|
+
|
26
|
+
module Searchgasm
|
27
|
+
|
28
|
+
# A class for describing the current version of a library. The version
|
29
|
+
# consists of three parts: the +major+ number, the +minor+ number, and the
|
30
|
+
# +tiny+ (or +patch+) number.
|
31
|
+
class Version
|
32
|
+
|
33
|
+
include Comparable
|
34
|
+
|
35
|
+
# A convenience method for instantiating a new Version instance with the
|
36
|
+
# given +major+, +minor+, and +tiny+ components.
|
37
|
+
def self.[](major, minor, tiny)
|
38
|
+
new(major, minor, tiny)
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :major, :minor, :tiny
|
42
|
+
|
43
|
+
# Create a new Version object with the given components.
|
44
|
+
def initialize(major, minor, tiny)
|
45
|
+
@major, @minor, @tiny = major, minor, tiny
|
46
|
+
end
|
47
|
+
|
48
|
+
# Compare this version to the given +version+ object.
|
49
|
+
def <=>(version)
|
50
|
+
to_i <=> version.to_i
|
51
|
+
end
|
52
|
+
|
53
|
+
# Converts this version object to a string, where each of the three
|
54
|
+
# version components are joined by the '.' character. E.g., 2.0.0.
|
55
|
+
def to_s
|
56
|
+
@to_s ||= [@major, @minor, @tiny].join(".")
|
57
|
+
end
|
58
|
+
|
59
|
+
# Converts this version to a canonical integer that may be compared
|
60
|
+
# against other version objects.
|
61
|
+
def to_i
|
62
|
+
@to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_a
|
66
|
+
[@major, @minor, @tiny]
|
67
|
+
end
|
68
|
+
|
69
|
+
MAJOR = 0
|
70
|
+
MINOR = 9
|
71
|
+
TINY = 0
|
72
|
+
|
73
|
+
# The current version as a Version instance
|
74
|
+
CURRENT = new(MAJOR, MINOR, TINY)
|
75
|
+
# The current version as a String
|
76
|
+
STRING = CURRENT.to_s
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
data/lib/searchgasm.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "searchgasm/active_record/protection"
|
3
|
+
require "searchgasm/active_record/base"
|
4
|
+
require "searchgasm/active_record/associations"
|
5
|
+
require "searchgasm/version"
|
6
|
+
require "searchgasm/search/utilities"
|
7
|
+
require "searchgasm/search/condition"
|
8
|
+
require "searchgasm/search/conditions"
|
9
|
+
require "searchgasm/search/base"
|