Class: Hash

Inherits:
Object show all
Defined in:
lib/strokedb/core_ext/hash.rb,
lib/strokedb/core_ext/blank.rb,
lib/strokedb/sync/diff/hash.rb,
lib/strokedb/util/serialization.rb

Overview

:nodoc:

Direct Known Subclasses

StrokeDB::LazyMappingHash

Instance Method Summary collapse

Instance Method Details

#_stroke_split_merge_result(result) ⇒ Object

In case of conflict, result is copied to result1,2 and nullified.



182
183
184
# File 'lib/strokedb/sync/diff/hash.rb', line 182

def _stroke_split_merge_result(result)
  return [result.dup, result.dup, nil]
end

#except(*keys) ⇒ Object



11
12
13
# File 'lib/strokedb/core_ext/hash.rb', line 11

def except(*keys)
  reject { |key,| keys.include?(key.to_sym) or keys.include?(key.to_s) }
end

#reverse_merge(other_hash) ⇒ Object



15
16
17
# File 'lib/strokedb/core_ext/hash.rb', line 15

def reverse_merge(other_hash)
  other_hash.merge(self)
end

#reverse_merge!(other_hash) ⇒ Object



19
20
21
# File 'lib/strokedb/core_ext/hash.rb', line 19

def reverse_merge!(other_hash)
  replace(reverse_merge(other_hash))
end

#stringify_keysObject



4
5
6
7
8
9
# File 'lib/strokedb/core_ext/hash.rb', line 4

def stringify_keys
  inject({}) do |options, (key, value)|
    options[key.to_s] = value
    options
  end
end

#stroke_diff(to) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/strokedb/sync/diff/hash.rb', line 3

def stroke_diff(to)
  return super(to) unless to.is_a?(Hash)
  return nil if self == to
  
  all_keys = self.keys | to.keys
  
  deleted_slots  = []
  inserted_slots = {}
  diffed_slots   = {}
  
  all_keys.each do |k|
    unless to.key?(k)
      deleted_slots << k
    else
      unless self.key?(k)
        inserted_slots[k] = to[k] 
      else
        diff = self[k].stroke_diff(to[k]) 
        diffed_slots[k] = diff if diff
      end
    end
  end
  [deleted_slots, inserted_slots, diffed_slots]
end

#stroke_merge(patch1, patch2) ⇒ Object

Hash conflict may occur: 1) If key accures in conflicting groups

(e.g. deleted v.s. inserted, deleted vs. diffed)

2) If slot diff yields conflict



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/strokedb/sync/diff/hash.rb', line 44

def stroke_merge(patch1, patch2)
  unless patch1 && patch2
    return _stroke_automerged(stroke_patch(patch1 || patch2))
  end
  
  del1, ins1, dif1 = patch1[0].dup, patch1[1].dup, patch1[2].dup
  del2, ins2, dif2 = patch2[0].dup, patch2[1].dup, patch2[2].dup
  
  # TEMPORARY: inconsistency check
  conflict_d1i2 = del1 & ins2.keys
  conflict_d2i1 = del2 & ins1.keys
  conflict_i1f2 = ins1.keys & dif2.keys
  conflict_i2f1 = ins2.keys & dif1.keys
  
  unless conflict_d1i2.empty? && conflict_d2i1.empty? && 
         conflict_i1f2.empty? && conflict_i2f1.empty? 
    raise "Fatal inconsistency on stroke_merge detected!"
  end
  
  overlapping_keys = ((del1 + ins1.keys + dif1.keys) & 
                      (del2 + ins2.keys + dif2.keys))
  # make hash for faster inclusion tests
  overlapping_keys_hash = overlapping_keys.inject({}) do |h, k|
    h[k] = 1; h
  end
  
  result  = self.dup
  
  # 1. Merge non-overlapping updates
  (del1 + del2 - overlapping_keys).each do |k|
    del1.delete(k)
    del2.delete(k)
    result.delete(k)
  end
  ins1.dup.each do |k, v|
    unless overlapping_keys_hash[k]
      result[k] = v 
      ins1.delete(k)
    end
  end
  ins2.dup.each do |k, v|
    unless overlapping_keys_hash[k]
      result[k] = v 
      ins2.delete(k)
    end
  end
  dif1.dup.each do |k, diff|
    unless overlapping_keys_hash[k]
      result[k] = stroke_patch(diff)
      dif1.delete(k)
    end
  end
  dif2.dup.each do |k, diff|
    unless overlapping_keys_hash[k]
      result[k] = stroke_patch(diff)
      dif2.delete(k)
    end
  end

  # 2. Resolve overlapping keys
  #
  # Overlapping key may be in such pairs:
  #
  #  [dif, dif] <- possible conflict
  #  [dif, ins] <- anomaly
  #  [ins, ins] <- possible conflict
  #  [del, ins] <- anomaly
  #  [del, dif] <- conflict
  #  [del, del] <- not a conflict
  #
  #  (and in reverse order as well)
  
  result1 = nil
  result2 = nil
  del1.each do |k|
    if ins2.key?(k)
      raise "Fatal inconsistency on stroke_merge detected: delete + insert"
    elsif dif2.key?(k) 
      # Conflict. Split result if not splitted.
      result1, result2, result = _stroke_split_merge_result(result) if result
      result1.delete(k)
      result2[k] = result2[k].stroke_patch(dif2[k])
    else # [del, del]
      if result
        result.delete(k)
      else
        result1.delete(k)
        result2.delete(k)
      end
    end
  end
  dif1.each do |k, diff|
    if ins2.key?(k)
      raise "Fatal inconsistency on stroke_merge detected: diff + insert"
    elsif dif2.key?(k) # possible conflict
      conflict, r1, r2 = self[k].stroke_merge(diff, dif2[k])
      if conflict
        result1, result2, result = _stroke_split_merge_result(result) if result
        result1[k] = r1
        result2[k] = r2
      else
        if result
          result[k] = r2
        else
          result1[k] = r2
          result2[k] = r2
        end
      end
    else # [dif, del] <- conflict
      # Conflict. Split result if not splitted.
      result1, result2, result = _stroke_split_merge_result(result) if result
      result1[k] = result1[k].stroke_patch(diff)
      result2.delete(k)
    end
  end
  ins1.each do |k, obj|
    if ins2.key?(k) # possible conflict
      if obj != ins2[k]
        result1, result2, result = _stroke_split_merge_result(result) if result
        result1[k] = obj
        result2[k] = ins2[k]
      else
        if result
          result[k] = obj
        else
          result1[k] = obj
          result2[k] = obj
        end
      end
    else # delete or diff
      raise "Fatal inconsistency on stroke_merge detected: insert + (delete|diff)"
    end
  end
  
  result ? _stroke_automerged(result) : _stroke_conflicted(result1, result2)
end

#stroke_patch(patch) ⇒ Object



28
29
30
31
32
33
34
35
36
37
# File 'lib/strokedb/sync/diff/hash.rb', line 28

def stroke_patch(patch)
  return self unless patch
  return patch[1] if patch[0] == PATCH_REPLACE
  res = self.dup
  deleted_slots, inserted_slots, diffed_slots = patch
  deleted_slots.each {|k| res.delete(k) }
  res.merge!(inserted_slots)
  diffed_slots.each {|k,v| res[k] = self[k].stroke_patch(v) }
  res
end

#to_rawObject



12
13
14
15
16
17
18
19
20
# File 'lib/strokedb/util/serialization.rb', line 12

def to_raw
  raw_hash = {}
  map do |k,v|
    _k = k.respond_to?(:to_raw) ? k.to_raw : k
    _v = v.respond_to?(:to_raw) ? v.to_raw : v
    raw_hash[_k] = _v
  end
  raw_hash
end