searchgasm 0.9.6 → 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG +2 -0
  2. data/Manifest +34 -21
  3. data/{README.mdown → README.rdoc} +96 -63
  4. data/Rakefile +1 -1
  5. data/examples/README.rdoc +4 -0
  6. data/lib/searchgasm/active_record/associations.rb +40 -42
  7. data/lib/searchgasm/active_record/base.rb +75 -61
  8. data/lib/searchgasm/condition/base.rb +127 -0
  9. data/lib/searchgasm/condition/begins_with.rb +20 -0
  10. data/lib/searchgasm/condition/child_of.rb +11 -0
  11. data/lib/searchgasm/condition/contains.rb +20 -0
  12. data/lib/searchgasm/condition/descendant_of.rb +24 -0
  13. data/lib/searchgasm/condition/does_not_equal.rb +28 -0
  14. data/lib/searchgasm/condition/ends_with.rb +20 -0
  15. data/lib/searchgasm/condition/equals.rb +20 -0
  16. data/lib/searchgasm/condition/greater_than.rb +25 -0
  17. data/lib/searchgasm/condition/greater_than_or_equal_to.rb +20 -0
  18. data/lib/searchgasm/condition/inclusive_descendant_of.rb +13 -0
  19. data/lib/searchgasm/condition/keywords.rb +33 -0
  20. data/lib/searchgasm/condition/less_than.rb +25 -0
  21. data/lib/searchgasm/condition/less_than_or_equal_to.rb +20 -0
  22. data/lib/searchgasm/condition/sibling_of.rb +16 -0
  23. data/lib/searchgasm/condition/tree.rb +16 -0
  24. data/lib/searchgasm/conditions/base.rb +221 -0
  25. data/lib/searchgasm/conditions/protection.rb +30 -0
  26. data/lib/searchgasm/config.rb +137 -0
  27. data/lib/searchgasm/helpers/form_helper.rb +159 -0
  28. data/lib/searchgasm/helpers/search_helper.rb +178 -0
  29. data/lib/searchgasm/helpers/utilities_helper.rb +125 -0
  30. data/lib/searchgasm/search/base.rb +73 -179
  31. data/lib/searchgasm/search/conditions.rb +42 -166
  32. data/lib/searchgasm/search/ordering.rb +149 -0
  33. data/lib/searchgasm/search/pagination.rb +69 -0
  34. data/lib/searchgasm/search/protection.rb +61 -0
  35. data/lib/searchgasm/utilities.rb +30 -0
  36. data/lib/searchgasm/version.rb +44 -47
  37. data/lib/searchgasm.rb +57 -21
  38. data/searchgasm.gemspec +71 -46
  39. data/test/test_active_record_associations.rb +1 -1
  40. data/test/test_active_record_base.rb +4 -4
  41. data/test/test_condition.rb +143 -0
  42. data/test/{test_searchgasm_conditions.rb → test_conditions_base.rb} +43 -33
  43. data/test/test_search_base.rb +189 -0
  44. data/test/test_search_ordering.rb +91 -0
  45. data/test/test_search_pagination.rb +56 -0
  46. data/test/test_search_protection.rb +35 -0
  47. metadata +70 -45
  48. data/lib/searchgasm/search/condition.rb +0 -105
  49. data/lib/searchgasm/search/condition_types/begins_with_condition.rb +0 -26
  50. data/lib/searchgasm/search/condition_types/child_of_condition.rb +0 -17
  51. data/lib/searchgasm/search/condition_types/contains_condition.rb +0 -26
  52. data/lib/searchgasm/search/condition_types/descendant_of_condition.rb +0 -30
  53. data/lib/searchgasm/search/condition_types/does_not_equal_condition.rb +0 -34
  54. data/lib/searchgasm/search/condition_types/ends_with_condition.rb +0 -26
  55. data/lib/searchgasm/search/condition_types/equals_condition.rb +0 -26
  56. data/lib/searchgasm/search/condition_types/greater_than_condition.rb +0 -31
  57. data/lib/searchgasm/search/condition_types/greater_than_or_equal_to_condition.rb +0 -26
  58. data/lib/searchgasm/search/condition_types/inclusive_descendant_of_condition.rb +0 -19
  59. data/lib/searchgasm/search/condition_types/keywords_condition.rb +0 -39
  60. data/lib/searchgasm/search/condition_types/less_than_condition.rb +0 -31
  61. data/lib/searchgasm/search/condition_types/less_than_or_equal_to_condition.rb +0 -26
  62. data/lib/searchgasm/search/condition_types/sibling_of_condition.rb +0 -22
  63. data/lib/searchgasm/search/condition_types/tree_condition.rb +0 -20
  64. data/lib/searchgasm/search/utilities.rb +0 -34
  65. data/test/test_searchgasm_base.rb +0 -185
  66. data/test/test_searchgasm_condition_types.rb +0 -143
@@ -1,171 +1,47 @@
1
- module BinaryLogic
2
- module Searchgasm
3
- module Search
4
- class Conditions
5
- include Utilities
6
-
7
- attr_accessor :klass, :protect, :relationship_name, :scope
8
-
9
- class << self
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
- def initialize(klass, init_values = {})
31
- self.klass = klass
32
- add_klass_conditions!
33
- add_column_conditions!
34
- add_associations!
35
- self.value = init_values
36
- end
37
-
38
- # Setup methods for searching
39
- [:all, :average, :calculate, :count, :find, :first, :maximum, :minimum, :sum].each do |method|
40
- class_eval <<-end_eval
41
- def #{method}(*args)
42
- self.value = args.extract_options!
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
- private
107
- def add_associations!
108
- klass.reflect_on_all_associations.each do |association|
109
- self.class.class_eval <<-end_eval
110
- def #{association.name}
111
- if @#{association.name}.nil?
112
- @#{association.name} = self.class.new(#{association.class_name})
113
- @#{association.name}.relationship_name = "#{association.name}"
114
- self.objects << @#{association.name}
115
- end
116
- @#{association.name}
117
- end
118
-
119
- def #{association.name}=(value); #{association.name}.value = value; end
120
- def reset_#{association.name}!; objects.delete(#{association.name}); @#{association.name} = nil; end
121
- end_eval
122
- end
123
- end
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
@@ -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 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
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
- # 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
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
- attr_reader :major, :minor, :tiny
40
+ attr_reader :major, :minor, :tiny
42
41
 
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
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
- # Compare this version to the given +version+ object.
49
- def <=>(version)
50
- to_i <=> version.to_i
51
- end
47
+ # Compare this version to the given +version+ object.
48
+ def <=>(version)
49
+ to_i <=> version.to_i
50
+ end
52
51
 
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
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
- # 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
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
- MAJOR = 0
70
- MINOR = 9
71
- TINY = 6
68
+ MAJOR = 0
69
+ MINOR = 9
70
+ TINY = 7
72
71
 
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
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