garland 1.2.2 → 1.3.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.
- checksums.yaml +4 -4
- data/lib/garland_rails.rb +6 -263
- data/lib/garland_rails/base.rb +34 -0
- data/lib/garland_rails/extend.rb +25 -0
- data/lib/garland_rails/push.rb +133 -0
- data/lib/garland_rails/snapshot.rb +53 -0
- data/lib/garland_rails/utils.rb +86 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89056f6fadd0309a8e4829b17bdd788cd0643bc9
|
4
|
+
data.tar.gz: 33567da54c9bae23ffa87d26bb95ed3fc95795b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17f6eec112cd6888fb8e2eac3bf654facaaf1195658739ee39ad1f2ccc2d834dc491e78b42dc92bc3516a8b62e9d655f27f70a7469318e8ce01c5cc44ceea812
|
7
|
+
data.tar.gz: 811a1222362482f420e5fc4db1b915a2a15f60da6a9b84ce39c66d2398c4db02b144167ca69577888d0fd5bd37d7986bf5c575abdca2022059eea54aea5195ce
|
data/lib/garland_rails.rb
CHANGED
@@ -1,267 +1,10 @@
|
|
1
1
|
module GarlandRails
|
2
2
|
DIFF = true
|
3
3
|
SNAPSHOT = false
|
4
|
-
|
5
|
-
module Extend
|
6
|
-
def self.included(base)
|
7
|
-
base.extend(self)
|
8
|
-
end
|
9
|
-
|
10
|
-
def has_many(name, scope = nil, options = {}, &extension)
|
11
|
-
name_superclass = name.to_s.classify.constantize.superclass
|
12
|
-
if name_superclass == GarlandRails::Base
|
13
|
-
# if there are no scope and there are some options,
|
14
|
-
# scope will contain options, and we need to swap them
|
15
|
-
if scope.class == Hash
|
16
|
-
options = scope
|
17
|
-
scope = nil
|
18
|
-
end
|
19
|
-
|
20
|
-
belongs_to_type = self.name
|
21
|
-
scope = -> { where(belongs_to_type: belongs_to_type) }
|
22
|
-
options = options.merge(foreign_key: "belongs_to_id")
|
23
|
-
end
|
24
|
-
|
25
|
-
super(name, scope, options, &extension)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
class Base < Garland
|
30
|
-
self.table_name = "garlands"
|
31
|
-
|
32
|
-
validates :entity, presence: true
|
33
|
-
validates_inclusion_of :entity_type, in: [DIFF, SNAPSHOT]
|
34
|
-
|
35
|
-
# for given parent object, described by "belongs_to_id" and "belongs_to_type",
|
36
|
-
# there are only one record of each type which is head or tail
|
37
|
-
validates_uniqueness_of :type, scope: [
|
38
|
-
:belongs_to_id,
|
39
|
-
:belongs_to_type,
|
40
|
-
:next_id,
|
41
|
-
], conditions: -> { where("next_id IS NULL OR previous_id IS NULL") }
|
42
|
-
|
43
|
-
# everything seems to work without `polymorphic: true`,
|
44
|
-
# but we will set it just for accordance to docs
|
45
|
-
def self.belongs_to(name, scope = nil, options = {}, &extension)
|
46
|
-
if scope.class == Hash
|
47
|
-
options = scope
|
48
|
-
scope = nil
|
49
|
-
end
|
50
|
-
options = options.merge(foreign_key: "belongs_to_id")
|
51
|
-
|
52
|
-
super(name, scope, options, &extension)
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.push(args)
|
56
|
-
return nil unless args.class == Hash
|
57
|
-
|
58
|
-
if args[:hash]
|
59
|
-
hash = args[:hash]
|
60
|
-
return nil unless hash.class == Hash
|
61
|
-
|
62
|
-
belongs_to = args[:belongs_to]
|
63
|
-
if belongs_to
|
64
|
-
belongs_to_params = self._split_belongs_to(belongs_to)
|
65
|
-
belongs_to_id, belongs_to_type =
|
66
|
-
belongs_to_params.values_at(:belongs_to_id, :belongs_to_type)
|
67
|
-
end
|
68
|
-
else
|
69
|
-
hash = args
|
70
|
-
belongs_to = nil
|
71
|
-
belongs_to_id = nil
|
72
|
-
belongs_to_type = nil
|
73
|
-
end
|
74
|
-
|
75
|
-
head = self.head(belongs_to)
|
76
|
-
if head
|
77
|
-
diff = self.insert_diff(hash, belongs_to)
|
78
|
-
else
|
79
|
-
diff = self.init(hash, belongs_to)
|
80
|
-
end
|
81
|
-
|
82
|
-
diff
|
83
|
-
end
|
84
|
-
|
85
|
-
def self.thread(belongs_to = nil)
|
86
|
-
if belongs_to
|
87
|
-
return self.where(
|
88
|
-
"belongs_to_id = ? AND belongs_to_type = ?",
|
89
|
-
belongs_to.id, table_type(belongs_to)
|
90
|
-
)
|
91
|
-
else
|
92
|
-
return self.where("belongs_to_id is null AND belongs_to_type is null")
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def self.tail(belongs_to = nil)
|
97
|
-
self.thread(belongs_to).find_by(previous_id: nil)
|
98
|
-
end
|
99
|
-
|
100
|
-
def self.head(belongs_to = nil)
|
101
|
-
self.thread(belongs_to).find_by(next_id: nil)
|
102
|
-
end
|
103
|
-
|
104
|
-
def self.last_diff(belongs_to = nil)
|
105
|
-
head = self.head(belongs_to)
|
106
|
-
return nil unless head
|
107
|
-
|
108
|
-
head.previous
|
109
|
-
end
|
110
|
-
|
111
|
-
def next
|
112
|
-
Garland.find(self.next_id) if self.next_id
|
113
|
-
end
|
114
|
-
|
115
|
-
def previous
|
116
|
-
Garland.find(self.previous_id) if self.previous_id
|
117
|
-
end
|
118
|
-
|
119
|
-
def safe_eval_entity
|
120
|
-
return nil unless self.entity =~ /\[.*\]/ || self.entity =~ /{.*}/
|
121
|
-
|
122
|
-
eval(self.entity)
|
123
|
-
end
|
124
|
-
|
125
|
-
def self.table_type(record)
|
126
|
-
record.class.name
|
127
|
-
end
|
128
|
-
|
129
|
-
def self.any?(belongs_to = nil)
|
130
|
-
self.thread(belongs_to).any?
|
131
|
-
end
|
132
|
-
|
133
|
-
def self.init(hash, belongs_to = nil)
|
134
|
-
common_props = self._split_belongs_to(belongs_to)
|
135
|
-
|
136
|
-
tail_props = common_props.merge(entity: {}.to_s, entity_type: SNAPSHOT)
|
137
|
-
brand_new_tail = self.new(tail_props)
|
138
|
-
|
139
|
-
diff = HashDiffSym.diff({}, hash)
|
140
|
-
first_diff_props = common_props.merge(entity: diff.to_s, entity_type: DIFF)
|
141
|
-
first_diff = self.new(first_diff_props)
|
142
|
-
|
143
|
-
head_props = common_props.merge(entity: hash.to_s, entity_type: SNAPSHOT)
|
144
|
-
brand_new_head = self.new(head_props)
|
145
|
-
|
146
|
-
self.transaction do
|
147
|
-
ActiveRecord::Base.connection.create_savepoint("savepoint_before_init")
|
148
|
-
|
149
|
-
# first id: tail ({})
|
150
|
-
# second id: head (latest snapshot)
|
151
|
-
# third+: diffs
|
152
|
-
unless brand_new_tail.save
|
153
|
-
Rails.logger.error("Unable to create new tail with props '#{tail_props}'")
|
154
|
-
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
155
|
-
return nil
|
156
|
-
end
|
157
|
-
|
158
|
-
# belongs_to validations were in `brand_new_tail.save`
|
159
|
-
# here and below validations may be skipped as long as we check for continuity later
|
160
|
-
brand_new_head.save(validate: false)
|
161
|
-
first_diff.save(validate: false)
|
162
|
-
brand_new_tail.update_attribute(:next_id, first_diff.id)
|
163
|
-
first_diff.update_attribute(:previous_id, brand_new_tail.id)
|
164
|
-
first_diff.update_attribute(:next_id, brand_new_head.id)
|
165
|
-
brand_new_head.update_attribute(:previous_id, first_diff.id)
|
166
|
-
|
167
|
-
unless self.continuous?(belongs_to)
|
168
|
-
Rails.logger.error("Initialized garland is not continuous")
|
169
|
-
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_init")
|
170
|
-
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
171
|
-
return nil
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
first_diff
|
176
|
-
end
|
177
|
-
|
178
|
-
def self.insert_diff(hash, belongs_to = nil)
|
179
|
-
head = self.head(belongs_to)
|
180
|
-
last_diff = self.find_by(id: head.previous_id)
|
181
|
-
common_props = self._split_belongs_to(belongs_to)
|
182
|
-
|
183
|
-
diff = HashDiffSym.diff(head.safe_eval_entity, hash)
|
184
|
-
return unless diff.any?
|
185
|
-
|
186
|
-
new_diff_props = common_props.merge(
|
187
|
-
entity: diff.to_s,
|
188
|
-
entity_type: DIFF,
|
189
|
-
previous_id: last_diff.id,
|
190
|
-
next_id: head.id,
|
191
|
-
)
|
192
|
-
new_diff = self.new(new_diff_props)
|
193
|
-
|
194
|
-
self.transaction do
|
195
|
-
ActiveRecord::Base.connection.create_savepoint("savepoint_before_insert_diff")
|
196
|
-
|
197
|
-
# insert_diff should not use skipping valudatuons methods
|
198
|
-
# because we don't want to check for continuity on every push
|
199
|
-
unless new_diff.save
|
200
|
-
Rails.logger.error("Unable to create new_diff with props '#{new_diff_props}'")
|
201
|
-
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
202
|
-
return nil
|
203
|
-
end
|
204
|
-
|
205
|
-
last_diff.next_id = new_diff.id
|
206
|
-
unless last_diff.save
|
207
|
-
Rails.logger.error("Unable to save last_diff with 'next_id' = '#{new_diff.id}'")
|
208
|
-
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
209
|
-
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
210
|
-
return nil
|
211
|
-
end
|
212
|
-
|
213
|
-
head.previous_id = new_diff.id
|
214
|
-
unless head.save
|
215
|
-
Rails.logger.error("Unable to save head with 'previous_id' = '#{new_diff.id}'")
|
216
|
-
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
217
|
-
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
218
|
-
return nil
|
219
|
-
end
|
220
|
-
|
221
|
-
head.entity = hash.to_s
|
222
|
-
unless head.save
|
223
|
-
Rails.logger.error("Unable to save head with 'entity' = '#{hash}'")
|
224
|
-
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
225
|
-
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
226
|
-
return nil
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
new_diff
|
231
|
-
end
|
232
|
-
|
233
|
-
def self.continuous?(belongs_to = nil)
|
234
|
-
tail = self.tail(belongs_to)
|
235
|
-
head = self.head(belongs_to)
|
236
|
-
return false unless tail && head
|
237
|
-
|
238
|
-
current_bulb = tail
|
239
|
-
current_hash = tail.safe_eval_entity
|
240
|
-
items_counted = 1
|
241
|
-
while current_bulb.next_id do
|
242
|
-
items_counted += 1
|
243
|
-
current_bulb = current_bulb.next
|
244
|
-
if current_bulb.entity_type == DIFF
|
245
|
-
current_hash = HashDiffSym.patch!(current_hash, current_bulb.safe_eval_entity)
|
246
|
-
else
|
247
|
-
break
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
items_counted == self.thread(belongs_to).size && current_hash == head.safe_eval_entity
|
252
|
-
end
|
253
|
-
|
254
|
-
private
|
255
|
-
def self._split_belongs_to(belongs_to)
|
256
|
-
if belongs_to
|
257
|
-
belongs_to_id = belongs_to.id
|
258
|
-
belongs_to_type = table_type(belongs_to)
|
259
|
-
else
|
260
|
-
belongs_to_id = nil
|
261
|
-
belongs_to_type = nil
|
262
|
-
end
|
263
|
-
|
264
|
-
{ belongs_to_id: belongs_to_id, belongs_to_type: belongs_to_type }
|
265
|
-
end
|
266
|
-
end
|
267
4
|
end
|
5
|
+
|
6
|
+
require "garland_rails/extend.rb"
|
7
|
+
require "garland_rails/base.rb"
|
8
|
+
require "garland_rails/utils.rb"
|
9
|
+
require "garland_rails/snapshot.rb"
|
10
|
+
require "garland_rails/push.rb"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module GarlandRails
|
2
|
+
class Base < Garland
|
3
|
+
self.table_name = "garlands"
|
4
|
+
|
5
|
+
validates :entity, presence: true
|
6
|
+
validates_inclusion_of :entity_type, in: [DIFF, SNAPSHOT]
|
7
|
+
|
8
|
+
# for given parent object, described by "belongs_to_id" and "belongs_to_type",
|
9
|
+
# there are only one record of each type which is head or tail
|
10
|
+
validates_uniqueness_of :type, scope: [
|
11
|
+
:belongs_to_id,
|
12
|
+
:belongs_to_type,
|
13
|
+
:next_id,
|
14
|
+
], conditions: -> { where("next_id IS NULL OR previous_id IS NULL") }
|
15
|
+
|
16
|
+
# Minitest raises "No visible difference in the *#inspect output" error
|
17
|
+
# while comparing Garland objects without this.
|
18
|
+
def ==(other)
|
19
|
+
self.attributes == other.attributes
|
20
|
+
end
|
21
|
+
|
22
|
+
# everything seems to work without `polymorphic: true`,
|
23
|
+
# but we will set it just for accordance to docs
|
24
|
+
def self.belongs_to(name, scope = nil, options = {}, &extension)
|
25
|
+
if scope.class == Hash
|
26
|
+
options = scope
|
27
|
+
scope = nil
|
28
|
+
end
|
29
|
+
options = options.merge(foreign_key: "belongs_to_id")
|
30
|
+
|
31
|
+
super(name, scope, options, &extension)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module GarlandRails
|
2
|
+
module Extend
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(self)
|
5
|
+
end
|
6
|
+
|
7
|
+
def has_many(name, scope = nil, options = {}, &extension)
|
8
|
+
name_superclass = name.to_s.classify.constantize.superclass
|
9
|
+
if name_superclass == GarlandRails::Base
|
10
|
+
# if there are no scope and there are some options,
|
11
|
+
# scope will contain options, and we need to swap them
|
12
|
+
if scope.class == Hash
|
13
|
+
options = scope
|
14
|
+
scope = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
belongs_to_type = self.name
|
18
|
+
scope = -> { where(belongs_to_type: belongs_to_type) }
|
19
|
+
options = options.merge(foreign_key: "belongs_to_id")
|
20
|
+
end
|
21
|
+
|
22
|
+
super(name, scope, options, &extension)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module GarlandRails
|
2
|
+
class Base < Garland
|
3
|
+
def self.push(args)
|
4
|
+
return nil unless args.class == Hash
|
5
|
+
|
6
|
+
if args[:hash]
|
7
|
+
hash = args[:hash]
|
8
|
+
return nil unless hash.class == Hash
|
9
|
+
|
10
|
+
belongs_to = args[:belongs_to]
|
11
|
+
if belongs_to
|
12
|
+
belongs_to_params = self._split_belongs_to(belongs_to)
|
13
|
+
belongs_to_id, belongs_to_type =
|
14
|
+
belongs_to_params.values_at(:belongs_to_id, :belongs_to_type)
|
15
|
+
end
|
16
|
+
else
|
17
|
+
hash = args
|
18
|
+
belongs_to = nil
|
19
|
+
belongs_to_id = nil
|
20
|
+
belongs_to_type = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
head = self.head(belongs_to)
|
24
|
+
if head
|
25
|
+
diff = self.insert_diff(hash, belongs_to)
|
26
|
+
else
|
27
|
+
diff = self.init(hash, belongs_to)
|
28
|
+
end
|
29
|
+
|
30
|
+
diff
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.init(hash, belongs_to = nil)
|
34
|
+
common_props = self._split_belongs_to(belongs_to)
|
35
|
+
|
36
|
+
tail_props = common_props.merge(entity: {}.to_s, entity_type: SNAPSHOT)
|
37
|
+
brand_new_tail = self.new(tail_props)
|
38
|
+
|
39
|
+
diff = HashDiffSym.diff({}, hash)
|
40
|
+
first_diff_props = common_props.merge(entity: diff.to_s, entity_type: DIFF)
|
41
|
+
first_diff = self.new(first_diff_props)
|
42
|
+
|
43
|
+
head_props = common_props.merge(entity: hash.to_s, entity_type: SNAPSHOT)
|
44
|
+
brand_new_head = self.new(head_props)
|
45
|
+
|
46
|
+
self.transaction do
|
47
|
+
ActiveRecord::Base.connection.create_savepoint("savepoint_before_init")
|
48
|
+
|
49
|
+
# first id: tail ({})
|
50
|
+
# second id: head (latest snapshot)
|
51
|
+
# third+: diffs
|
52
|
+
unless brand_new_tail.save
|
53
|
+
Rails.logger.error("Unable to create new tail with props '#{tail_props}'")
|
54
|
+
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# belongs_to validations were in `brand_new_tail.save`
|
59
|
+
# here and below validations may be skipped as long as we check for continuity later
|
60
|
+
brand_new_head.save(validate: false)
|
61
|
+
first_diff.save(validate: false)
|
62
|
+
brand_new_tail.update_attribute(:next_id, first_diff.id)
|
63
|
+
first_diff.update_attribute(:previous_id, brand_new_tail.id)
|
64
|
+
first_diff.update_attribute(:next_id, brand_new_head.id)
|
65
|
+
brand_new_head.update_attribute(:previous_id, first_diff.id)
|
66
|
+
|
67
|
+
unless self.continuous?(belongs_to)
|
68
|
+
Rails.logger.error("Initialized garland is not continuous")
|
69
|
+
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_init")
|
70
|
+
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
first_diff
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.insert_diff(hash, belongs_to = nil)
|
79
|
+
head = self.head(belongs_to)
|
80
|
+
last_diff = self.find_by(id: head.previous_id)
|
81
|
+
common_props = self._split_belongs_to(belongs_to)
|
82
|
+
|
83
|
+
diff = HashDiffSym.diff(head.safe_eval_entity, hash)
|
84
|
+
return unless diff.any?
|
85
|
+
|
86
|
+
new_diff_props = common_props.merge(
|
87
|
+
entity: diff.to_s,
|
88
|
+
entity_type: DIFF,
|
89
|
+
previous_id: last_diff.id,
|
90
|
+
next_id: head.id,
|
91
|
+
)
|
92
|
+
new_diff = self.new(new_diff_props)
|
93
|
+
|
94
|
+
self.transaction do
|
95
|
+
ActiveRecord::Base.connection.create_savepoint("savepoint_before_insert_diff")
|
96
|
+
|
97
|
+
# insert_diff should not use skipping valudatuons methods
|
98
|
+
# because we don't want to check for continuity on every push
|
99
|
+
unless new_diff.save
|
100
|
+
Rails.logger.error("Unable to create new_diff with props '#{new_diff_props}'")
|
101
|
+
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
102
|
+
return nil
|
103
|
+
end
|
104
|
+
|
105
|
+
last_diff.next_id = new_diff.id
|
106
|
+
unless last_diff.save
|
107
|
+
Rails.logger.error("Unable to save last_diff with 'next_id' = '#{new_diff.id}'")
|
108
|
+
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
109
|
+
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
110
|
+
return nil
|
111
|
+
end
|
112
|
+
|
113
|
+
head.previous_id = new_diff.id
|
114
|
+
unless head.save
|
115
|
+
Rails.logger.error("Unable to save head with 'previous_id' = '#{new_diff.id}'")
|
116
|
+
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
117
|
+
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
|
121
|
+
head.entity = hash.to_s
|
122
|
+
unless head.save
|
123
|
+
Rails.logger.error("Unable to save head with 'entity' = '#{hash}'")
|
124
|
+
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
125
|
+
ActiveRecord::Base.connection.release_savepoint("savepoint_before_init")
|
126
|
+
return nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
new_diff
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module GarlandRails
|
2
|
+
class Base < Garland
|
3
|
+
def snapshot(patch_direction = :backward)
|
4
|
+
case patch_direction
|
5
|
+
when :forward
|
6
|
+
approach_nearest_snapshot_method = :previous
|
7
|
+
back_method = :next
|
8
|
+
patch_method = :patch!
|
9
|
+
when :backward
|
10
|
+
approach_nearest_snapshot_method = :next
|
11
|
+
back_method = :previous
|
12
|
+
patch_method = :unpatch!
|
13
|
+
end
|
14
|
+
|
15
|
+
if self.entity_type == SNAPSHOT
|
16
|
+
snapshot = self.safe_eval_entity
|
17
|
+
else
|
18
|
+
nearest_snapshot = self
|
19
|
+
while nearest_snapshot.entity_type != SNAPSHOT do
|
20
|
+
nearest_snapshot = nearest_snapshot.send(approach_nearest_snapshot_method)
|
21
|
+
end
|
22
|
+
|
23
|
+
snapshot = nearest_snapshot.safe_eval_entity
|
24
|
+
current_bulb = nearest_snapshot.send(back_method)
|
25
|
+
loop do
|
26
|
+
break if current_bulb == self
|
27
|
+
HashDiffSym.send(patch_method, snapshot, current_bulb.safe_eval_entity)
|
28
|
+
current_bulb = current_bulb.send(back_method)
|
29
|
+
end
|
30
|
+
|
31
|
+
# this comes from asymmetry of Garland model
|
32
|
+
#
|
33
|
+
# entity => producing snapshot:
|
34
|
+
#
|
35
|
+
# tail => {}
|
36
|
+
# diff1 => hash1
|
37
|
+
# diff2 => hash2
|
38
|
+
# diff3 => hash3
|
39
|
+
# head => hash3
|
40
|
+
if patch_direction == :forward
|
41
|
+
HashDiffSym.send(patch_method, snapshot, current_bulb.safe_eval_entity)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Garland.new(
|
46
|
+
entity_type: SNAPSHOT,
|
47
|
+
entity: snapshot.to_s,
|
48
|
+
created_at: self.created_at,
|
49
|
+
type: self.type,
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module GarlandRails
|
2
|
+
class Base < Garland
|
3
|
+
# REVIEW: shouldn't thread(), tail() and head() have the same args as push()?
|
4
|
+
def self.thread(belongs_to = nil)
|
5
|
+
if belongs_to
|
6
|
+
return self.where(
|
7
|
+
"belongs_to_id = ? AND belongs_to_type = ?",
|
8
|
+
belongs_to.id, table_type(belongs_to)
|
9
|
+
)
|
10
|
+
else
|
11
|
+
return self.where("belongs_to_id is null AND belongs_to_type is null")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.tail(belongs_to = nil)
|
16
|
+
self.thread(belongs_to).find_by(previous_id: nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.head(belongs_to = nil)
|
20
|
+
self.thread(belongs_to).find_by(next_id: nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.last_diff(belongs_to = nil)
|
24
|
+
head = self.head(belongs_to)
|
25
|
+
return nil unless head
|
26
|
+
|
27
|
+
head.previous
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.continuous?(belongs_to = nil)
|
31
|
+
tail = self.tail(belongs_to)
|
32
|
+
head = self.head(belongs_to)
|
33
|
+
return false unless tail && head
|
34
|
+
|
35
|
+
current_bulb = tail
|
36
|
+
current_hash = tail.safe_eval_entity
|
37
|
+
items_counted = 1
|
38
|
+
while current_bulb.next_id do
|
39
|
+
items_counted += 1
|
40
|
+
current_bulb = current_bulb.next
|
41
|
+
if current_bulb.entity_type == DIFF
|
42
|
+
current_hash = HashDiffSym.patch!(current_hash, current_bulb.safe_eval_entity)
|
43
|
+
else
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
items_counted == self.thread(belongs_to).size && current_hash == head.safe_eval_entity
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.table_type(record)
|
52
|
+
record.class.name
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.any?(belongs_to = nil)
|
56
|
+
self.thread(belongs_to).any?
|
57
|
+
end
|
58
|
+
|
59
|
+
def next
|
60
|
+
Garland.find(self.next_id) if self.next_id
|
61
|
+
end
|
62
|
+
|
63
|
+
def previous
|
64
|
+
Garland.find(self.previous_id) if self.previous_id
|
65
|
+
end
|
66
|
+
|
67
|
+
def safe_eval_entity
|
68
|
+
return nil unless self.entity =~ /\[.*\]/ || self.entity =~ /{.*}/
|
69
|
+
|
70
|
+
eval(self.entity)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def self._split_belongs_to(belongs_to)
|
75
|
+
if belongs_to
|
76
|
+
belongs_to_id = belongs_to.id
|
77
|
+
belongs_to_type = table_type(belongs_to)
|
78
|
+
else
|
79
|
+
belongs_to_id = nil
|
80
|
+
belongs_to_type = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
{ belongs_to_id: belongs_to_id, belongs_to_type: belongs_to_type }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: garland
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Morozov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashdiff_sym
|
@@ -33,6 +33,11 @@ extra_rdoc_files: []
|
|
33
33
|
files:
|
34
34
|
- lib/garland.rb
|
35
35
|
- lib/garland_rails.rb
|
36
|
+
- lib/garland_rails/base.rb
|
37
|
+
- lib/garland_rails/extend.rb
|
38
|
+
- lib/garland_rails/push.rb
|
39
|
+
- lib/garland_rails/snapshot.rb
|
40
|
+
- lib/garland_rails/utils.rb
|
36
41
|
- lib/generators/garland_rails/install_generator.rb
|
37
42
|
- lib/generators/garland_rails/templates/install_migration.rb
|
38
43
|
- lib/generators/garland_rails/templates/uninstall_migration.rb
|
@@ -59,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
64
|
version: '0'
|
60
65
|
requirements: []
|
61
66
|
rubyforge_project:
|
62
|
-
rubygems_version: 2.
|
67
|
+
rubygems_version: 2.5.2
|
63
68
|
signing_key:
|
64
69
|
specification_version: 4
|
65
70
|
summary: HashDiff Rails storage
|