culturecode_stagehand 0.3.1 → 0.4.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/stagehand/production.rb +9 -5
- data/lib/stagehand/staging/commit_entry.rb +1 -1
- data/lib/stagehand/staging/synchronizer.rb +76 -28
- data/lib/stagehand/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd5f6596af77820e4b0fda648cf3a575cef70dd8
|
4
|
+
data.tar.gz: 73711aa60b690c1a9a7715d5ce26934d79e7727f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b685a7c6bd6f6c8638c86f8ff3a316ad2147942b7dcaa378daea28fbc31421cce4105097ac1a3a418d8a6639891e107c5663ee9a5177bdb25770f2d3cd555ac
|
7
|
+
data.tar.gz: 7f629766d1d1af995dc0870bf15bdc6a2c04c96105859cc5cdc4388cb104c4be11b7f0df4c17987f1b8e54a1fd6447e6847fd792196e346ee8f49da17a558cbe
|
data/lib/stagehand/production.rb
CHANGED
@@ -20,7 +20,7 @@ module Stagehand
|
|
20
20
|
|
21
21
|
return unless attributes.present?
|
22
22
|
|
23
|
-
is_new =
|
23
|
+
is_new = matching(staging_record, table_name).update_all(attributes).zero?
|
24
24
|
|
25
25
|
# Ensure we always return a record, even when updating instead of creating
|
26
26
|
Record.new.tap do |record|
|
@@ -30,11 +30,11 @@ module Stagehand
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def delete(staging_record, table_name = nil)
|
33
|
-
|
33
|
+
matching(staging_record, table_name).delete_all
|
34
34
|
end
|
35
35
|
|
36
36
|
def exists?(staging_record, table_name = nil)
|
37
|
-
|
37
|
+
matching(staging_record, table_name).exists?
|
38
38
|
end
|
39
39
|
|
40
40
|
# Returns true if the staging record's attributes are different from the production record's attributes
|
@@ -44,9 +44,13 @@ module Stagehand
|
|
44
44
|
production_record_attributes(staging_record, table_name) != staging_record_attributes(staging_record, table_name)
|
45
45
|
end
|
46
46
|
|
47
|
+
def find(*args)
|
48
|
+
matching(*args).first
|
49
|
+
end
|
50
|
+
|
47
51
|
# Returns a scope that limits results any occurrences of the specified record.
|
48
52
|
# Record can be specified by passing a staging record, or an id and table_name.
|
49
|
-
def
|
53
|
+
def matching(staging_record, table_name = nil)
|
50
54
|
table_name, id = Stagehand::Key.generate(staging_record, :table_name => table_name)
|
51
55
|
prepare_to_modify(table_name)
|
52
56
|
return Record.where(:id => id)
|
@@ -60,7 +64,7 @@ module Stagehand
|
|
60
64
|
end
|
61
65
|
|
62
66
|
def production_record_attributes(staging_record, table_name = nil)
|
63
|
-
Record.connection.select_one(
|
67
|
+
Record.connection.select_one(matching(staging_record, table_name))
|
64
68
|
end
|
65
69
|
|
66
70
|
def staging_record_attributes(staging_record, table_name = nil)
|
@@ -109,7 +109,7 @@ module Stagehand
|
|
109
109
|
private
|
110
110
|
|
111
111
|
def build_production_record
|
112
|
-
production_record = Stagehand::Production.
|
112
|
+
production_record = Stagehand::Production.find(record_id, table_name)
|
113
113
|
return unless production_record
|
114
114
|
|
115
115
|
production_record = record_class.new(production_record.attributes)
|
@@ -2,6 +2,9 @@ module Stagehand
|
|
2
2
|
module Staging
|
3
3
|
module Synchronizer
|
4
4
|
extend self
|
5
|
+
mattr_accessor :schemas_match
|
6
|
+
|
7
|
+
BATCH_SIZE = 1000
|
5
8
|
|
6
9
|
# Immediately attempt to sync the changes from the block if possible
|
7
10
|
# The block is wrapped in a transaction to prevent changes to records while being synced
|
@@ -14,22 +17,33 @@ module Stagehand
|
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
17
|
-
def auto_sync(
|
18
|
-
scope = autosyncable_entries.limit(1000)
|
19
|
-
|
20
|
+
def auto_sync(polling_delay = 5.seconds)
|
20
21
|
loop do
|
21
|
-
|
22
|
-
sleep(
|
22
|
+
sync(BATCH_SIZE)
|
23
|
+
sleep(polling_delay) if polling_delay
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
27
|
def sync(limit = nil)
|
27
|
-
|
28
|
+
synced_count = 0
|
29
|
+
deleted_count = 0
|
30
|
+
|
31
|
+
iterate_autosyncable_entries do |entry|
|
32
|
+
sync_entries(entry)
|
33
|
+
synced_count += 1
|
34
|
+
deleted_count += CommitEntry.matching(entry).delete_all
|
35
|
+
break if synced_count == limit
|
36
|
+
end
|
37
|
+
|
38
|
+
Rails.logger.info "Synced #{synced_count} entries"
|
39
|
+
Rails.logger.info "Removed #{deleted_count} stale entries"
|
40
|
+
|
41
|
+
return synced_count
|
28
42
|
end
|
29
43
|
|
30
44
|
def sync_all
|
31
45
|
loop do
|
32
|
-
entries = CommitEntry.order(:id => :desc).limit(
|
46
|
+
entries = CommitEntry.order(:id => :desc).limit(BATCH_SIZE).to_a
|
33
47
|
break unless entries.present?
|
34
48
|
|
35
49
|
latest_entries = entries.uniq(&:key)
|
@@ -48,44 +62,78 @@ module Stagehand
|
|
48
62
|
|
49
63
|
private
|
50
64
|
|
65
|
+
# Lazily iterate through millions of commit entries
|
66
|
+
# Returns commit entries in ID descending order
|
67
|
+
def iterate_autosyncable_entries(&block)
|
68
|
+
sessions = CommitEntry.order(:id => :desc).distinct.pluck(:session)
|
69
|
+
offset = 0
|
70
|
+
|
71
|
+
while sessions.present?
|
72
|
+
autosyncable_entries(:session => sessions.shift(30)).offset(offset).limit(BATCH_SIZE).each do |entry|
|
73
|
+
with_confirmed_autosyncability(entry, &block)
|
74
|
+
end
|
75
|
+
offset += BATCH_SIZE
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Executes the code in the block if the record referred to by the entry is in fact, autosyncable.
|
80
|
+
# This confirmation is used to guard against writes to the record that occur after loading an initial list of
|
81
|
+
# entries that are autosyncable, but before the record is actually synced. To prevent this, a lock on the record
|
82
|
+
# is acquired and then the record's autosync eligibility is rechecked before calling the block.
|
83
|
+
# NOTE: This method must be called from within a transaction
|
84
|
+
def with_confirmed_autosyncability(entry, &block)
|
85
|
+
ActiveRecord::Base.transaction do
|
86
|
+
CommitEntry.connection.execute("SELECT 1 FROM #{entry.table_name} WHERE id = #{entry.record_id}")
|
87
|
+
block.call(entry) if autosyncable_entries(:record_id => entry.record_id, :table_name => entry.table_name).exists?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns commit entries in ID descending order
|
92
|
+
def autosyncable_entries(scope = nil)
|
93
|
+
entries = CommitEntry.content_operations.not_in_progress
|
94
|
+
|
95
|
+
unless Configuration.ghost_mode?
|
96
|
+
subquery = CommitEntry.group('record_id, table_name').having('count(commit_id) = 0').where(scope)
|
97
|
+
entries = entries.joins("JOIN (#{subquery.select('MAX(id) AS max_id').to_sql}) subquery ON id = max_id")
|
98
|
+
end
|
99
|
+
|
100
|
+
return entries.order(:id => :desc)
|
101
|
+
end
|
102
|
+
|
51
103
|
def sync_checklist(checklist)
|
52
|
-
|
53
|
-
|
104
|
+
ActiveRecord::Base.transaction do
|
105
|
+
sync_entries(checklist.syncing_entries)
|
106
|
+
CommitEntry.delete(checklist.affected_entries)
|
107
|
+
end
|
54
108
|
end
|
55
109
|
|
56
110
|
def sync_entries(entries)
|
57
111
|
return 0 if Configuration.single_connection? # Avoid deadlocking if the databases are the same
|
58
|
-
|
59
112
|
raise SchemaMismatch unless schemas_match?
|
60
113
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
114
|
+
entries = Array.wrap(entries)
|
115
|
+
|
116
|
+
entries.each do |entry|
|
117
|
+
Rails.logger.info "Synchronizing #{entry.table_name} #{entry.record_id}" if entry.content_operation?
|
118
|
+
if entry.delete_operation?
|
119
|
+
Stagehand::Production.delete(entry)
|
120
|
+
elsif entry.save_operation?
|
121
|
+
Stagehand::Production.save(entry)
|
69
122
|
end
|
70
123
|
end
|
71
124
|
|
72
125
|
return entries.length
|
73
126
|
end
|
74
127
|
|
75
|
-
def autosyncable_entries
|
76
|
-
if Configuration.ghost_mode?
|
77
|
-
CommitEntry
|
78
|
-
else
|
79
|
-
CommitEntry.where(:id =>
|
80
|
-
CommitEntry.select('MAX(id) AS id').content_operations.not_in_progress.group('record_id, table_name').having('count(commit_id) = 0'))
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
128
|
def schemas_match?
|
129
|
+
return schemas_match unless schemas_match.nil?
|
130
|
+
|
85
131
|
versions_scope = ActiveRecord::SchemaMigration.order(:version)
|
86
132
|
staging_versions = Stagehand::Database.staging_connection.select_values(versions_scope)
|
87
133
|
production_versions = Stagehand::Database.production_connection.select_values(versions_scope)
|
88
|
-
|
134
|
+
self.schemas_match = staging_versions == production_versions
|
135
|
+
|
136
|
+
return schemas_match
|
89
137
|
end
|
90
138
|
end
|
91
139
|
end
|
data/lib/stagehand/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: culturecode_stagehand
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicholas Jakobsen
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-04-
|
12
|
+
date: 2016-04-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|