strokedb 0.0.2

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.
Files changed (59) hide show
  1. data/CONTRIBUTORS +7 -0
  2. data/CREDITS +13 -0
  3. data/README +44 -0
  4. data/bin/sdbc +2 -0
  5. data/lib/config/config.rb +161 -0
  6. data/lib/data_structures/inverted_list.rb +297 -0
  7. data/lib/data_structures/point_query.rb +24 -0
  8. data/lib/data_structures/skiplist.rb +302 -0
  9. data/lib/document/associations.rb +107 -0
  10. data/lib/document/callback.rb +11 -0
  11. data/lib/document/coercions.rb +57 -0
  12. data/lib/document/delete.rb +28 -0
  13. data/lib/document/document.rb +684 -0
  14. data/lib/document/meta.rb +261 -0
  15. data/lib/document/slot.rb +199 -0
  16. data/lib/document/util.rb +27 -0
  17. data/lib/document/validations.rb +704 -0
  18. data/lib/document/versions.rb +106 -0
  19. data/lib/document/virtualize.rb +82 -0
  20. data/lib/init.rb +57 -0
  21. data/lib/stores/chainable_storage.rb +57 -0
  22. data/lib/stores/inverted_list_index/inverted_list_file_storage.rb +56 -0
  23. data/lib/stores/inverted_list_index/inverted_list_index.rb +49 -0
  24. data/lib/stores/remote_store.rb +172 -0
  25. data/lib/stores/skiplist_store/chunk.rb +119 -0
  26. data/lib/stores/skiplist_store/chunk_storage.rb +21 -0
  27. data/lib/stores/skiplist_store/file_chunk_storage.rb +44 -0
  28. data/lib/stores/skiplist_store/memory_chunk_storage.rb +37 -0
  29. data/lib/stores/skiplist_store/skiplist_store.rb +217 -0
  30. data/lib/stores/store.rb +5 -0
  31. data/lib/sync/chain_sync.rb +38 -0
  32. data/lib/sync/diff.rb +126 -0
  33. data/lib/sync/lamport_timestamp.rb +81 -0
  34. data/lib/sync/store_sync.rb +79 -0
  35. data/lib/sync/stroke_diff/array.rb +102 -0
  36. data/lib/sync/stroke_diff/default.rb +21 -0
  37. data/lib/sync/stroke_diff/hash.rb +186 -0
  38. data/lib/sync/stroke_diff/string.rb +116 -0
  39. data/lib/sync/stroke_diff/stroke_diff.rb +9 -0
  40. data/lib/util/blankslate.rb +42 -0
  41. data/lib/util/ext/blank.rb +50 -0
  42. data/lib/util/ext/enumerable.rb +36 -0
  43. data/lib/util/ext/fixnum.rb +16 -0
  44. data/lib/util/ext/hash.rb +22 -0
  45. data/lib/util/ext/object.rb +8 -0
  46. data/lib/util/ext/string.rb +35 -0
  47. data/lib/util/inflect.rb +217 -0
  48. data/lib/util/java_util.rb +9 -0
  49. data/lib/util/lazy_array.rb +54 -0
  50. data/lib/util/lazy_mapping_array.rb +64 -0
  51. data/lib/util/lazy_mapping_hash.rb +46 -0
  52. data/lib/util/serialization.rb +29 -0
  53. data/lib/util/trigger_partition.rb +136 -0
  54. data/lib/util/util.rb +38 -0
  55. data/lib/util/xml.rb +6 -0
  56. data/lib/view/view.rb +55 -0
  57. data/script/console +70 -0
  58. data/strokedb.rb +75 -0
  59. metadata +148 -0
@@ -0,0 +1,102 @@
1
+ module StrokeDB
2
+ class ::Array
3
+ SDATPTAGS = {
4
+ '-' => PATCH_MINUS,
5
+ '+' => PATCH_PLUS,
6
+ '!' => PATCH_DIFF
7
+ }.freeze
8
+ def stroke_diff(to)
9
+ return super(to) unless Array === to
10
+ return nil if self == to
11
+
12
+ # sdiff: + - ! =
13
+ lcs_sdiff = ::Diff::LCS.sdiff(self, to)
14
+ patchset = lcs_sdiff.inject([]) do |patchset, change|
15
+ a = SDATPTAGS[change.action]
16
+ if a == PATCH_DIFF
17
+ patchset << [a, change.new_position, change.old_element.stroke_diff(change.new_element)]
18
+ elsif a == PATCH_MINUS
19
+ patchset << [a, change.new_position]
20
+ elsif a == PATCH_PLUS
21
+ patchset << [a, change.new_position, change.new_element]
22
+ end
23
+ patchset
24
+ end
25
+ patchset.empty? ? nil : patchset
26
+ end
27
+
28
+ def stroke_patch(patch)
29
+ return self unless patch
30
+ return patch[1] if patch[0] == PATCH_REPLACE
31
+ res = self.dup
32
+ patch.each do |change|
33
+ _stroke_elementary_patch(res, change[1], change)
34
+ end
35
+ res
36
+ end
37
+
38
+ def stroke_merge(patch1, patch2)
39
+ unless patch1 && patch2
40
+ return _stroke_automerged(stroke_patch(patch1 || patch2))
41
+ end
42
+
43
+ patch1 = patch1.dup
44
+ patch2 = patch2.dup
45
+
46
+ c1 = patch1.shift
47
+ c2 = patch2.shift
48
+
49
+ offset1 = 0
50
+ offset2 = 0
51
+ result = self.dup
52
+ result1 = nil
53
+ result2 = nil
54
+
55
+ while c1 && c2
56
+ while c1 && (p1 = c1[1] + offset1) && (p2 = c2[1] + offset2) && p1 < p2
57
+ offset2 += _stroke_elementary_patch(result, p1, c1)
58
+ c1 = patch1.shift
59
+ end
60
+
61
+ if p1 == p2
62
+ raise "TODO conflict resolution!"
63
+
64
+ c1 = patch1.shift
65
+ end
66
+
67
+ while c1 && c2 && (p1 = c1[1] + offset1) && (p2 = c2[1] + offset2) && p2 < p1
68
+ offset1 += _stroke_elementary_patch(result, p2, c2)
69
+ c2 = patch2.shift
70
+ end
71
+ end
72
+
73
+ # Tail (one of two)
74
+ while c1
75
+ offset2 += _stroke_elementary_patch(result, c1[1] + offset1, c1)
76
+ c1 = patch1.shift
77
+ end
78
+ while c2
79
+ offset1 += _stroke_elementary_patch(result, c2[1] + offset2, c2)
80
+ c2 = patch2.shift
81
+ end
82
+ result ? _stroke_automerged(result) : _stroke_conflicted(result1, result2)
83
+ end
84
+
85
+ private
86
+ def _stroke_elementary_patch(result, pos, change)
87
+ a = change[0]
88
+ case a
89
+ when PATCH_MINUS
90
+ result.delete_at(pos)
91
+ -1
92
+ when PATCH_PLUS
93
+ result[pos, 0] = [change[2]]
94
+ +1
95
+ when PATCH_DIFF
96
+ result[pos] = result[pos].stroke_patch(change[2])
97
+ 0
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,21 @@
1
+ module StrokeDB
2
+ class ::Object
3
+ def stroke_diff(to)
4
+ self == to ? nil : [PATCH_REPLACE, to]
5
+ end
6
+ def stroke_patch(patch)
7
+ patch ? patch[1] : self
8
+ end
9
+ def stroke_merge(patch1, patch2) # => is_conflict, result1, result2
10
+ r1 = self.stroke_patch(patch1)
11
+ r2 = self.stroke_patch(patch2)
12
+ [r1 != r2, r1, r2]
13
+ end
14
+ def _stroke_automerged(r)
15
+ [false, r, r]
16
+ end
17
+ def _stroke_conflicted(r1, r2)
18
+ [true, r1, r2]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,186 @@
1
+ module StrokeDB
2
+ class ::Hash
3
+ def stroke_diff(to)
4
+ return super(to) unless Hash === to
5
+ return nil if self == to
6
+
7
+ all_keys = self.keys | to.keys
8
+
9
+ deleted_slots = []
10
+ inserted_slots = {}
11
+ diffed_slots = {}
12
+
13
+ all_keys.each do |k|
14
+ unless to.key?(k)
15
+ deleted_slots << k
16
+ else
17
+ unless self.key?(k)
18
+ inserted_slots[k] = to[k]
19
+ else
20
+ diff = self[k].stroke_diff(to[k])
21
+ diffed_slots[k] = diff if diff
22
+ end
23
+ end
24
+ end
25
+ [deleted_slots, inserted_slots, diffed_slots]
26
+ end
27
+
28
+ def stroke_patch(patch)
29
+ return self unless patch
30
+ return patch[1] if patch[0] == PATCH_REPLACE
31
+ res = self.dup
32
+ deleted_slots, inserted_slots, diffed_slots = patch
33
+ deleted_slots.each {|k| res.delete(k) }
34
+ res.merge!(inserted_slots)
35
+ diffed_slots.each {|k,v| res[k] = self[k].stroke_patch(v) }
36
+ res
37
+ end
38
+
39
+ # Hash conflict may occur:
40
+ # 1) If key accures in conflicting groups
41
+ # (e.g. deleted v.s. inserted, deleted vs. diffed)
42
+ # 2) If slot diff yields conflict
43
+ #
44
+ def stroke_merge(patch1, patch2)
45
+ unless patch1 && patch2
46
+ return _stroke_automerged(stroke_patch(patch1 || patch2))
47
+ end
48
+
49
+ del1, ins1, dif1 = patch1[0].dup, patch1[1].dup, patch1[2].dup
50
+ del2, ins2, dif2 = patch2[0].dup, patch2[1].dup, patch2[2].dup
51
+
52
+ # TEMPORARY: inconsistency check
53
+ conflict_d1i2 = del1 & ins2.keys
54
+ conflict_d2i1 = del2 & ins1.keys
55
+ conflict_i1f2 = ins1.keys & dif2.keys
56
+ conflict_i2f1 = ins2.keys & dif1.keys
57
+
58
+ unless conflict_d1i2.empty? && conflict_d2i1.empty? &&
59
+ conflict_i1f2.empty? && conflict_i2f1.empty?
60
+ raise "Fatal inconsistency on stroke_merge detected!"
61
+ end
62
+
63
+ overlapping_keys = ((del1 + ins1.keys + dif1.keys) &
64
+ (del2 + ins2.keys + dif2.keys))
65
+ # make hash for faster inclusion tests
66
+ overlapping_keys_hash = overlapping_keys.inject({}) do |h, k|
67
+ h[k] = 1; h
68
+ end
69
+
70
+ result = self.dup
71
+
72
+ # 1. Merge non-overlapping updates
73
+ (del1 + del2 - overlapping_keys).each do |k|
74
+ del1.delete(k)
75
+ del2.delete(k)
76
+ result.delete(k)
77
+ end
78
+ ins1.dup.each do |k, v|
79
+ unless overlapping_keys_hash[k]
80
+ result[k] = v
81
+ ins1.delete(k)
82
+ end
83
+ end
84
+ ins2.dup.each do |k, v|
85
+ unless overlapping_keys_hash[k]
86
+ result[k] = v
87
+ ins2.delete(k)
88
+ end
89
+ end
90
+ dif1.dup.each do |k, diff|
91
+ unless overlapping_keys_hash[k]
92
+ result[k] = stroke_patch(diff)
93
+ dif1.delete(k)
94
+ end
95
+ end
96
+ dif2.dup.each do |k, diff|
97
+ unless overlapping_keys_hash[k]
98
+ result[k] = stroke_patch(diff)
99
+ dif2.delete(k)
100
+ end
101
+ end
102
+
103
+ # 2. Resolve overlapping keys
104
+ #
105
+ # Overlapping key may be in such pairs:
106
+ #
107
+ # [dif, dif] <- possible conflict
108
+ # [dif, ins] <- anomaly
109
+ # [ins, ins] <- possible conflict
110
+ # [del, ins] <- anomaly
111
+ # [del, dif] <- conflict
112
+ # [del, del] <- not a conflict
113
+ #
114
+ # (and in reverse order as well)
115
+
116
+ result1 = nil
117
+ result2 = nil
118
+ del1.each do |k|
119
+ if ins2.key?(k)
120
+ raise "Fatal inconsistency on stroke_merge detected: delete + insert"
121
+ elsif dif2.key?(k)
122
+ # Conflict. Split result if not splitted.
123
+ result1, result2, result = _stroke_split_merge_result(result) if result
124
+ result1.delete(k)
125
+ result2[k] = result2[k].stroke_patch(dif2[k])
126
+ else # [del, del]
127
+ if result
128
+ result.delete(k)
129
+ else
130
+ result1.delete(k)
131
+ result2.delete(k)
132
+ end
133
+ end
134
+ end
135
+ dif1.each do |k, diff|
136
+ if ins2.key?(k)
137
+ raise "Fatal inconsistency on stroke_merge detected: diff + insert"
138
+ elsif dif2.key?(k) # possible conflict
139
+ conflict, r1, r2 = self[k].stroke_merge(diff, dif2[k])
140
+ if conflict
141
+ result1, result2, result = _stroke_split_merge_result(result) if result
142
+ result1[k] = r1
143
+ result2[k] = r2
144
+ else
145
+ if result
146
+ result[k] = r2
147
+ else
148
+ result1[k] = r2
149
+ result2[k] = r2
150
+ end
151
+ end
152
+ else # [dif, del] <- conflict
153
+ # Conflict. Split result if not splitted.
154
+ result1, result2, result = _stroke_split_merge_result(result) if result
155
+ result1[k] = result1[k].stroke_patch(diff)
156
+ result2.delete(k)
157
+ end
158
+ end
159
+ ins1.each do |k, obj|
160
+ if ins2.key?(k) # possible conflict
161
+ if obj != ins2[k]
162
+ result1, result2, result = _stroke_split_merge_result(result) if result
163
+ result1[k] = obj
164
+ result2[k] = ins2[k]
165
+ else
166
+ if result
167
+ result[k] = obj
168
+ else
169
+ result1[k] = obj
170
+ result2[k] = obj
171
+ end
172
+ end
173
+ else # delete or diff
174
+ raise "Fatal inconsistency on stroke_merge detected: insert + (delete|diff)"
175
+ end
176
+ end
177
+
178
+ result ? _stroke_automerged(result) : _stroke_conflicted(result1, result2)
179
+ end
180
+
181
+ # In case of conflict, result is copied to result{1,2} and nullified.
182
+ def _stroke_split_merge_result(result)
183
+ return [result.dup, result.dup, nil]
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,116 @@
1
+ module StrokeDB
2
+ class ::String
3
+ def stroke_diff(to)
4
+ return super(to) unless String === to
5
+ return nil if self == to
6
+
7
+ _f = self[0,2]
8
+ _t = to[0,2]
9
+ pfx = "@#"
10
+ # both are refs
11
+ return super(to) if _f == _t && _t == pfx
12
+ # one of items is ref, another is not.
13
+ return super(to) if _f == pfx || _t == pfx
14
+
15
+ lcs_diff = ::Diff::LCS.diff(self, to)
16
+ patchset = lcs_diff.map do |changes|
17
+ parts = []
18
+ last_part = changes.inject(nil) do |part, change|
19
+ if part && part[0] == change.action && part[3] == change.position - 1
20
+ part[3] += 1
21
+ part[2] << change.element
22
+ part
23
+ else
24
+ parts << part if part
25
+ # emit
26
+ [change.action, change.position, change.element, change.position]
27
+ end
28
+ end
29
+ parts << last_part if last_part
30
+ parts.empty? ? nil : parts
31
+ end.compact.inject([]) do |patches, ps|
32
+ ps.map do |p|
33
+ patches << if p[0] == '+'
34
+ [PATCH_PLUS, p[1], p[2]] # [+ position_in_b substr]
35
+ else
36
+ [PATCH_MINUS, p[1], p[2].size] # [- position_in_a length]
37
+ end
38
+ end
39
+ patches
40
+ end
41
+ #p patchset
42
+ patchset.empty? ? nil : patchset
43
+ end
44
+
45
+ def stroke_patch(patch)
46
+ return self unless patch
47
+ return patch[1] if patch[0] == PATCH_REPLACE
48
+
49
+ # Patch is a list of insertions and deletions.
50
+ # Deletion is indexed relative to base.
51
+ # Insertion is indexed relative to new string.
52
+ res = ""
53
+ ai = bj = 0
54
+ patch.each do |change|
55
+ action, position, element = change
56
+ case action
57
+ when PATCH_MINUS
58
+ d = position - ai
59
+ if d > 0
60
+ res << self[ai, d]
61
+ ai += d
62
+ bj += d
63
+ end
64
+ ai += element # element == length
65
+ when PATCH_PLUS
66
+ d = position - bj
67
+ if d > 0
68
+ res << self[ai, d]
69
+ ai += d
70
+ bj += d
71
+ end
72
+ bj += element.size
73
+ res << element
74
+ end
75
+ end
76
+ d = self.size - ai
77
+ res << self[ai, d] if d > 0
78
+ res
79
+ end
80
+
81
+ def stroke_merge(patch1, patch2)
82
+ # One patch is missing (i.e. no changes)
83
+ unless patch1 && patch2
84
+ return _stroke_automerged(stroke_patch(patch1 || patch2))
85
+ end
86
+
87
+ # Patch could be either PATCH_REPLACE or regular string diff.
88
+ # Thus, 4 cases:
89
+ #
90
+ # [replace, replace] -> possible conflict
91
+ # [replace, diff] -> conflict
92
+ # [diff, replace] -> conflict
93
+ # [diff, diff] -> possible conflict
94
+
95
+ # Code is verbose to be fast and clear
96
+ if patch1[0] == PATCH_REPLACE
97
+ if patch2[0] == PATCH_REPLACE # [replace, replace]
98
+ if patch1[1] != patch2[1]
99
+ return _stroke_conflicted(stroke_patch(patch1), stroke_patch(patch2))
100
+ else
101
+ return _stroke_automerged(stroke_patch(patch1))
102
+ end
103
+ else # [replace, diff]
104
+ return _stroke_conflicted(stroke_patch(patch1), stroke_patch(patch2))
105
+ end
106
+ else
107
+ if patch1[0] == PATCH_REPLACE # [diff, replace]
108
+ return _stroke_conflicted(stroke_patch(patch1), stroke_patch(patch2))
109
+ else
110
+ nil # [diff, diff] - see below
111
+ end
112
+ end
113
+ # TODO: ...
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'diff/lcs'
3
+
4
+ module StrokeDB
5
+ PATCH_REPLACE = 'R'.freeze
6
+ PATCH_PLUS = '+'.freeze
7
+ PATCH_MINUS = '-'.freeze
8
+ PATCH_DIFF = 'D'.freeze
9
+ end
@@ -0,0 +1,42 @@
1
+ unless defined?(BlankSlate)
2
+ class BlankSlate < BasicObject; end if defined?(BasicObject)
3
+
4
+ class BlankSlate
5
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
6
+ end
7
+ end
8
+
9
+ class BlankSlate
10
+ MethodMapping = {
11
+ '[]' => 'squarebracket',
12
+ '[]=' => 'squarebracket_set',
13
+ '<<' => 'leftarrow',
14
+ '*' => 'star',
15
+ '+' => 'plus',
16
+ '-' => 'minus',
17
+ '&' => 'bitwiseand',
18
+ '|' => 'bitwiseor',
19
+ '<=>' => 'spaceship',
20
+ '==' => 'equalequal',
21
+ '===' => 'tripleequal',
22
+ '=~' => 'regexmatch',
23
+ '`' => 'backtick',
24
+ } unless defined? MethodMapping
25
+ end
26
+
27
+ def BlankSlate superclass = nil
28
+ if superclass
29
+ (@blank_slates ||= {})[superclass] ||= Class.new(superclass) do
30
+ instance_methods.sort.each { |m|
31
+ unless m =~ /^__/
32
+ mname = "__#{::BlankSlate::MethodMapping[m.to_s] || m}"
33
+ class_eval "alias :'#{mname}' :'#{m}'"
34
+
35
+ undef_method m
36
+ end
37
+ }
38
+ end
39
+ else
40
+ BlankSlate
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ class Object
2
+ # An object is blank if it's nil, empty, or a whitespace string.
3
+ # For example, "", " ", nil, [], and {} are blank.
4
+ #
5
+ # This simplifies
6
+ # if !address.nil? && !address.empty?
7
+ # to
8
+ # if !address.blank?
9
+ def blank?
10
+ respond_to?(:empty?) ? empty? : !self
11
+ end
12
+ end
13
+
14
+ class NilClass #:nodoc:
15
+ def blank?
16
+ true
17
+ end
18
+ end
19
+
20
+ class FalseClass #:nodoc:
21
+ def blank?
22
+ true
23
+ end
24
+ end
25
+
26
+ class TrueClass #:nodoc:
27
+ def blank?
28
+ false
29
+ end
30
+ end
31
+
32
+ class Array #:nodoc:
33
+ alias_method :blank?, :empty?
34
+ end
35
+
36
+ class Hash #:nodoc:
37
+ alias_method :blank?, :empty?
38
+ end
39
+
40
+ class String #:nodoc:
41
+ def blank?
42
+ self !~ /\S/
43
+ end
44
+ end
45
+
46
+ class Numeric #:nodoc:
47
+ def blank?
48
+ false
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ # extracted from ActiveRecord (http://rubyforge.org/projects/activesupport/)
2
+
3
+ module Enumerable
4
+ # Collect an enumerable into sets, grouped by the result of a block. Useful,
5
+ # for example, for grouping records by date.
6
+ def group_by
7
+ inject({}) do |groups, element|
8
+ (groups[yield(element)] ||= []) << element
9
+ groups
10
+ end
11
+ end if RUBY_VERSION < '1.9'
12
+
13
+ # Map and each_with_index combined.
14
+ def map_with_index
15
+ collected=[]
16
+ each_with_index {|item, index| collected << yield(item, index) }
17
+ collected
18
+ end
19
+
20
+ alias :collect_with_index :map_with_index
21
+
22
+ def each_consecutive_pair
23
+ first = true
24
+ prev = nil
25
+
26
+ each do |val|
27
+ unless first
28
+ yield prev, val
29
+ else
30
+ first = false
31
+ end
32
+
33
+ prev = val
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ # extracted and adapted from ActiveSupport (http://rubyforge.org/projects/activesupport/)
2
+
3
+ class Fixnum
4
+ def multiple_of?(number)
5
+ self % number == 0
6
+ end
7
+
8
+ def even?
9
+ multiple_of? 2
10
+ end
11
+
12
+ def odd?
13
+ !even?
14
+ end
15
+ end
16
+
@@ -0,0 +1,22 @@
1
+ # extracted and adapted from ActiveRecord (http://rubyforge.org/projects/activesupport/)
2
+
3
+ class Hash
4
+ def stringify_keys
5
+ inject({}) do |options, (key, value)|
6
+ options[key.to_s] = value
7
+ options
8
+ end
9
+ end
10
+
11
+ def except(*keys)
12
+ reject { |key,| keys.include?(key.to_sym) or keys.include?(key.to_s) }
13
+ end
14
+
15
+ def reverse_merge(other_hash)
16
+ other_hash.merge(self)
17
+ end
18
+
19
+ def reverse_merge!(other_hash)
20
+ replace(reverse_merge(other_hash))
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ # extracted from ActiveRecord (http://rubyforge.org/projects/activesupport/)
2
+
3
+ class Object
4
+ unless respond_to?(:send!)
5
+ # Anticipating Ruby 1.9 neutering send
6
+ alias send! send
7
+ end
8
+ end
@@ -0,0 +1,35 @@
1
+ # extracted and adapted from ActiveRecord (http://rubyforge.org/projects/activesupport/)
2
+
3
+ class String
4
+ def ends_with?(suffix)
5
+ suffix = suffix.to_s
6
+ self[-suffix.length, suffix.length] == suffix
7
+ end
8
+
9
+ def camel_case
10
+ split('_').map{|e| e.capitalize}.join
11
+ end
12
+ alias :camelize :camel_case
13
+
14
+ def snake_case
15
+ gsub(/\B[A-Z][^A-Z]/, '_\&').downcase.gsub(' ', '_')
16
+ end
17
+
18
+ def tableize
19
+ words = snake_case.split('_')
20
+ words.last.replace words.last.plural
21
+ words.join('_')
22
+ end
23
+
24
+ def demodulize
25
+ gsub(/^.*::/, '')
26
+ end
27
+
28
+ def constantize
29
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
30
+ raise NameError, "#{self.inspect} is not a valid constant name!"
31
+ end
32
+
33
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
34
+ end
35
+ end