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.
- data/CONTRIBUTORS +7 -0
- data/CREDITS +13 -0
- data/README +44 -0
- data/bin/sdbc +2 -0
- data/lib/config/config.rb +161 -0
- data/lib/data_structures/inverted_list.rb +297 -0
- data/lib/data_structures/point_query.rb +24 -0
- data/lib/data_structures/skiplist.rb +302 -0
- data/lib/document/associations.rb +107 -0
- data/lib/document/callback.rb +11 -0
- data/lib/document/coercions.rb +57 -0
- data/lib/document/delete.rb +28 -0
- data/lib/document/document.rb +684 -0
- data/lib/document/meta.rb +261 -0
- data/lib/document/slot.rb +199 -0
- data/lib/document/util.rb +27 -0
- data/lib/document/validations.rb +704 -0
- data/lib/document/versions.rb +106 -0
- data/lib/document/virtualize.rb +82 -0
- data/lib/init.rb +57 -0
- data/lib/stores/chainable_storage.rb +57 -0
- data/lib/stores/inverted_list_index/inverted_list_file_storage.rb +56 -0
- data/lib/stores/inverted_list_index/inverted_list_index.rb +49 -0
- data/lib/stores/remote_store.rb +172 -0
- data/lib/stores/skiplist_store/chunk.rb +119 -0
- data/lib/stores/skiplist_store/chunk_storage.rb +21 -0
- data/lib/stores/skiplist_store/file_chunk_storage.rb +44 -0
- data/lib/stores/skiplist_store/memory_chunk_storage.rb +37 -0
- data/lib/stores/skiplist_store/skiplist_store.rb +217 -0
- data/lib/stores/store.rb +5 -0
- data/lib/sync/chain_sync.rb +38 -0
- data/lib/sync/diff.rb +126 -0
- data/lib/sync/lamport_timestamp.rb +81 -0
- data/lib/sync/store_sync.rb +79 -0
- data/lib/sync/stroke_diff/array.rb +102 -0
- data/lib/sync/stroke_diff/default.rb +21 -0
- data/lib/sync/stroke_diff/hash.rb +186 -0
- data/lib/sync/stroke_diff/string.rb +116 -0
- data/lib/sync/stroke_diff/stroke_diff.rb +9 -0
- data/lib/util/blankslate.rb +42 -0
- data/lib/util/ext/blank.rb +50 -0
- data/lib/util/ext/enumerable.rb +36 -0
- data/lib/util/ext/fixnum.rb +16 -0
- data/lib/util/ext/hash.rb +22 -0
- data/lib/util/ext/object.rb +8 -0
- data/lib/util/ext/string.rb +35 -0
- data/lib/util/inflect.rb +217 -0
- data/lib/util/java_util.rb +9 -0
- data/lib/util/lazy_array.rb +54 -0
- data/lib/util/lazy_mapping_array.rb +64 -0
- data/lib/util/lazy_mapping_hash.rb +46 -0
- data/lib/util/serialization.rb +29 -0
- data/lib/util/trigger_partition.rb +136 -0
- data/lib/util/util.rb +38 -0
- data/lib/util/xml.rb +6 -0
- data/lib/view/view.rb +55 -0
- data/script/console +70 -0
- data/strokedb.rb +75 -0
- 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,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,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,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
|