garland 0.1
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 +7 -0
- data/lib/garland.rb +1 -0
- data/lib/garland_rails.rb +216 -0
- metadata +60 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fcb60a9b283bda379d5796264e73b3061cb48a56
|
4
|
+
data.tar.gz: bb24cf3f58282f101eb2b5b02536d9ce0e6c54b0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6a5dd458eee1f2a1fadb994d43fd9c2583316e8fde7c212981297d95ebba86c774beb26cdd8a65533c8f2eb198eabb239fb4052dbb217a81015e98332727f5a0
|
7
|
+
data.tar.gz: be060085315be33b9355df2910959aa5dd0addba59a166933671ab5cbd977fa59ea105b1883fb9566e0015fe798d67eb8d6b00840b7386c2834c156db64e5bb6
|
data/lib/garland.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Garland = Class.new(ActiveRecord::Base)
|
@@ -0,0 +1,216 @@
|
|
1
|
+
module GarlandRails
|
2
|
+
DIFF = true
|
3
|
+
SNAPSHOT = false
|
4
|
+
|
5
|
+
class Base < Garland
|
6
|
+
self.table_name = "garlands"
|
7
|
+
|
8
|
+
validates :entity, presence: true
|
9
|
+
validates_inclusion_of :entity_type, in: [DIFF, SNAPSHOT]
|
10
|
+
|
11
|
+
# for given parent object, described by "belongs_to_id" and "belongs_to_type",
|
12
|
+
# there are only one record of each type which is head or tail
|
13
|
+
validates_uniqueness_of :type, scope: [
|
14
|
+
:belongs_to_id,
|
15
|
+
:belongs_to_type,
|
16
|
+
:next,
|
17
|
+
], conditions: -> { where("next IS NULL OR previous IS NULL") }
|
18
|
+
|
19
|
+
def self.belongs_to(type)
|
20
|
+
super type, foreign_key: "belongs_to_id"
|
21
|
+
validates type, presence: true
|
22
|
+
validates_inclusion_of :belongs_to_type, in: ["#{type.capitalize}"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.push(args)
|
26
|
+
return nil unless args.class == Hash
|
27
|
+
|
28
|
+
if args[:hash]
|
29
|
+
hash = args[:hash]
|
30
|
+
return nil unless hash.class == Hash
|
31
|
+
|
32
|
+
belongs_to = args[:belongs_to]
|
33
|
+
if belongs_to
|
34
|
+
belongs_to_params = self._split_belongs_to(belongs_to)
|
35
|
+
belongs_to_id = belongs_to_params[:belongs_to_id]
|
36
|
+
belongs_to_type = belongs_to_params[:belongs_to_type]
|
37
|
+
end
|
38
|
+
else
|
39
|
+
hash = args
|
40
|
+
belongs_to = nil
|
41
|
+
belongs_to_id = nil
|
42
|
+
belongs_to_type = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
head = self.head(belongs_to)
|
46
|
+
if head
|
47
|
+
diff = self.insert_diff(hash, belongs_to)
|
48
|
+
else
|
49
|
+
diff = self.init(hash, belongs_to)
|
50
|
+
end
|
51
|
+
|
52
|
+
diff
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.thread(belongs_to = nil)
|
56
|
+
if belongs_to
|
57
|
+
return self.where(
|
58
|
+
"belongs_to_id = ? AND belongs_to_type = ?",
|
59
|
+
belongs_to.id, table_type(belongs_to)
|
60
|
+
)
|
61
|
+
else
|
62
|
+
return self.where("belongs_to_id is null AND belongs_to_type is null")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.tail(belongs_to = nil)
|
67
|
+
self.thread(belongs_to).find_by(previous: nil)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.head(belongs_to = nil)
|
71
|
+
self.thread(belongs_to).find_by(next: nil)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.last_diff(belongs_to = nil)
|
75
|
+
head = self.head(belongs_to)
|
76
|
+
|
77
|
+
self.find_by(id: head.previous)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.table_type(record)
|
81
|
+
record.class.name
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.any?(belongs_to = nil)
|
85
|
+
self.thread(belongs_to).any?
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.init(hash, belongs_to = nil)
|
89
|
+
common_props = self._split_belongs_to(belongs_to)
|
90
|
+
|
91
|
+
tail_props = common_props.merge({ entity: {}.to_s, entity_type: SNAPSHOT })
|
92
|
+
brand_new_tail = self.new(tail_props)
|
93
|
+
|
94
|
+
diff = HashDiffSym.diff({}, hash)
|
95
|
+
first_diff_props = common_props.merge({ entity: diff.to_s, entity_type: DIFF })
|
96
|
+
first_diff = self.new(first_diff_props)
|
97
|
+
|
98
|
+
head_props = common_props.merge({ entity: hash.to_s, entity_type: SNAPSHOT })
|
99
|
+
brand_new_head = self.new(head_props)
|
100
|
+
|
101
|
+
self.transaction do
|
102
|
+
ActiveRecord::Base.connection.create_savepoint("savepoint_before_init")
|
103
|
+
|
104
|
+
# first id: tail ({})
|
105
|
+
# second id: head (latest snapshot)
|
106
|
+
# third+: diffs
|
107
|
+
unless brand_new_tail.save
|
108
|
+
Rails.logger.error("Unable to create new tail with props '#{tail_props}'")
|
109
|
+
return nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# belongs_to validations were in `brand_new_tail.save`
|
113
|
+
# here and below validations may be skipped as long as we check for continuity later
|
114
|
+
first_diff.save(validate: false)
|
115
|
+
brand_new_head.save(validate: false)
|
116
|
+
brand_new_tail.update_attribute(:next, first_diff.id)
|
117
|
+
first_diff.update_attribute(:previous, brand_new_tail.id)
|
118
|
+
first_diff.update_attribute(:next, brand_new_head.id)
|
119
|
+
brand_new_head.update_attribute(:previous, first_diff.id)
|
120
|
+
|
121
|
+
unless self.continuous?(belongs_to)
|
122
|
+
Rails.logger.error("Initialized garland is not continuous")
|
123
|
+
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_init")
|
124
|
+
return nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
first_diff
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.insert_diff(hash, belongs_to = nil)
|
132
|
+
head = self.head(belongs_to)
|
133
|
+
last_diff = self.find_by(id: head.previous)
|
134
|
+
common_props = self._split_belongs_to(belongs_to)
|
135
|
+
|
136
|
+
diff = HashDiffSym.diff(eval(head.entity), hash)
|
137
|
+
return unless diff.any?
|
138
|
+
|
139
|
+
new_diff_props = common_props.merge({
|
140
|
+
entity: diff.to_s,
|
141
|
+
entity_type: DIFF,
|
142
|
+
previous: last_diff.id,
|
143
|
+
next: head.id,
|
144
|
+
})
|
145
|
+
new_diff = self.new(new_diff_props)
|
146
|
+
|
147
|
+
self.transaction do
|
148
|
+
ActiveRecord::Base.connection.create_savepoint("savepoint_before_insert_diff")
|
149
|
+
|
150
|
+
# insert_diff should not use skipping valudatuons methods
|
151
|
+
# because we don't want to check for continuity on every push
|
152
|
+
unless new_diff.save
|
153
|
+
Rails.logger.error("Unable to create new_diff with props '#{new_diff_props}'")
|
154
|
+
return nil
|
155
|
+
end
|
156
|
+
|
157
|
+
last_diff.next = new_diff.id
|
158
|
+
unless last_diff.save
|
159
|
+
Rails.logger.error("Unable to save last_diff with 'next' = '#{new_diff.id}'")
|
160
|
+
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
161
|
+
return nil
|
162
|
+
end
|
163
|
+
|
164
|
+
head.previous = new_diff.id
|
165
|
+
unless head.save
|
166
|
+
Rails.logger.error("Unable to save head with 'previous' = '#{new_diff.id}'")
|
167
|
+
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
168
|
+
return nil
|
169
|
+
end
|
170
|
+
|
171
|
+
head.entity = hash.to_s
|
172
|
+
unless head.save
|
173
|
+
Rails.logger.error("Unable to save head with 'entity' = '#{hash.to_s}'")
|
174
|
+
ActiveRecord::Base.connection.exec_rollback_to_savepoint("savepoint_before_insert_diff")
|
175
|
+
return nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
new_diff
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.continuous?(belongs_to = nil)
|
183
|
+
tail = self.tail(belongs_to)
|
184
|
+
head = self.head(belongs_to)
|
185
|
+
return false unless tail && head
|
186
|
+
|
187
|
+
current_bulb = tail
|
188
|
+
current_hash = eval(tail.entity)
|
189
|
+
items_counted = 1
|
190
|
+
while current_bulb.next do
|
191
|
+
items_counted += 1
|
192
|
+
current_bulb = self.find_by(id: current_bulb.next)
|
193
|
+
if current_bulb.entity_type == DIFF
|
194
|
+
current_hash = HashDiffSym.patch!(current_hash, eval(current_bulb.entity))
|
195
|
+
else
|
196
|
+
break
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
items_counted == self.thread(belongs_to).size && current_hash == eval(head.entity)
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
def self._split_belongs_to(belongs_to)
|
205
|
+
if belongs_to
|
206
|
+
belongs_to_id = belongs_to.id
|
207
|
+
belongs_to_type = table_type(belongs_to)
|
208
|
+
else
|
209
|
+
belongs_to_id = nil
|
210
|
+
belongs_to_type = nil
|
211
|
+
end
|
212
|
+
|
213
|
+
{ belongs_to_id: belongs_to_id, belongs_to_type: belongs_to_type }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: garland
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexander Morozov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hashdiff_sym
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Provides GarlandRails::Base class for ActiveRecord, which allows to save
|
28
|
+
Hashes using snapshots and diffs.
|
29
|
+
email: ntcomp12@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/garland.rb
|
35
|
+
- lib/garland_rails.rb
|
36
|
+
homepage: https://github.com/kengho/garland
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata: {}
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 2.5.2
|
57
|
+
signing_key:
|
58
|
+
specification_version: 4
|
59
|
+
summary: HashDiff ActiveRecord storage
|
60
|
+
test_files: []
|