culturecode_stagehand 0.3.0 → 0.3.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 +4 -4
- data/lib/stagehand/auditor.rb +108 -0
- data/lib/stagehand/engine.rb +2 -1
- data/lib/stagehand/production.rb +10 -10
- data/lib/stagehand/staging/commit_entry.rb +8 -1
- data/lib/stagehand/staging/synchronizer.rb +18 -4
- data/lib/stagehand/staging.rb +0 -1
- data/lib/stagehand/version.rb +1 -1
- data/lib/tasks/stagehand_tasks.rake +10 -0
- metadata +3 -3
- data/lib/stagehand/staging/auditor.rb +0 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88258f8ab5b38d62a941024ddeef50d3e66ac13b
|
4
|
+
data.tar.gz: 9f1571c1823366b2a5b576d9e213a7585713e883
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8020df0d22695e6fe842996690431bff25595e5dc3c76f46cb3ff7054dc8e56fad5dab0dc93007d6cc81d84b9875e922f2d70f0b98a37f28a479e50b63e1c55
|
7
|
+
data.tar.gz: 391d375fd5dcc015f8ad867cc7495c476e2e1d652beddd18584ca2a4df262baf5aa0938f6ebb323357fd823055b73f8b32c8200b1dffb72829503e91bc32c176
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Stagehand
|
2
|
+
module Auditor
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def incomplete_commits
|
6
|
+
incomplete = []
|
7
|
+
|
8
|
+
incomplete_start_operations.each do |start_operation|
|
9
|
+
entries = records_until_match(start_operation, :asc, :operation => Staging::CommitEntry::START_OPERATION).to_a
|
10
|
+
incomplete << [start_operation.id, entries]
|
11
|
+
end
|
12
|
+
|
13
|
+
incomplete_end_operations.each do |end_operation|
|
14
|
+
entries = records_through_match(end_operation, :desc, :operation => Staging::CommitEntry::START_OPERATION).to_a
|
15
|
+
incomplete << [entries.last.id, entries]
|
16
|
+
end
|
17
|
+
|
18
|
+
return incomplete.to_h
|
19
|
+
end
|
20
|
+
|
21
|
+
def mismatched_records
|
22
|
+
output = {}
|
23
|
+
|
24
|
+
tables = Database.staging_connection.tables.select {|table_name| Schema::has_stagehand?(table_name) }
|
25
|
+
tables.each do |table_name|
|
26
|
+
print "\nChecking #{table_name} "
|
27
|
+
mismatched = {}
|
28
|
+
limit = 1000
|
29
|
+
index = 0
|
30
|
+
|
31
|
+
loop do
|
32
|
+
production_records = Database.production_connection.select_all("SELECT * FROM #{table_name} LIMIT #{limit} OFFSET #{limit * index}")
|
33
|
+
staging_records = Database.staging_connection.select_all("SELECT * FROM #{table_name} LIMIT #{limit} OFFSET #{limit * index}")
|
34
|
+
id_column = production_records.columns.index('id')
|
35
|
+
|
36
|
+
production_differences = production_records.rows - staging_records.rows
|
37
|
+
staging_differences = staging_records.rows - production_records.rows
|
38
|
+
|
39
|
+
production_differences.each do |row|
|
40
|
+
id = row[id_column]
|
41
|
+
mismatched[id] = {:production => row}
|
42
|
+
end
|
43
|
+
staging_differences.each do |row|
|
44
|
+
id = row[id_column]
|
45
|
+
mismatched[id] ||= {:staging => row}
|
46
|
+
end
|
47
|
+
|
48
|
+
if production_differences.present? || staging_differences.present?
|
49
|
+
print '!'
|
50
|
+
else
|
51
|
+
print '.'
|
52
|
+
end
|
53
|
+
|
54
|
+
index += 1
|
55
|
+
break unless staging_records.present? || production_records.present?
|
56
|
+
end
|
57
|
+
|
58
|
+
if mismatched.present?
|
59
|
+
print " #{mismatched.count} mismatched"
|
60
|
+
output[table_name] = mismatched
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
return output
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Incomplete End Operation that are not the last entry in their session
|
70
|
+
def incomplete_end_operations
|
71
|
+
last_entry_per_session = Staging::CommitEntry.group(:session).select('MAX(id) AS id')
|
72
|
+
return Staging::CommitEntry.uncontained.end_operations.where.not(:id => last_entry_per_session)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Incomplete Start on the same session as a subsequent start operation
|
76
|
+
def incomplete_start_operations
|
77
|
+
last_start_entry_per_session = Staging::CommitEntry.start_operations.group(:session).select('MAX(id) AS id')
|
78
|
+
return Staging::CommitEntry.uncontained.start_operations.where.not(:id => last_start_entry_per_session)
|
79
|
+
end
|
80
|
+
|
81
|
+
def records_until_match(start_entry, direction, match_attributes)
|
82
|
+
records_through_match(start_entry, direction, match_attributes)[0..-2]
|
83
|
+
end
|
84
|
+
|
85
|
+
def records_through_match(start_entry, direction, match_attributes)
|
86
|
+
last_entry = next_match(start_entry, direction, match_attributes)
|
87
|
+
return records_from(start_entry, direction).where.not("id #{exclusive_comparator(direction)} ?", last_entry)
|
88
|
+
end
|
89
|
+
|
90
|
+
def next_match(start_entry, direction, match_attributes)
|
91
|
+
records_from(start_entry, direction).where.not(:id => start_entry.id).where(match_attributes).first
|
92
|
+
end
|
93
|
+
|
94
|
+
def records_from(start_entry, direction)
|
95
|
+
scope = Staging::CommitEntry.where(:session => start_entry.session).where("id #{comparator(direction)} ?", start_entry.id)
|
96
|
+
scope = scope.reverse_order if direction == :desc
|
97
|
+
return scope
|
98
|
+
end
|
99
|
+
|
100
|
+
def comparator(direction)
|
101
|
+
exclusive_comparator(direction) + '='
|
102
|
+
end
|
103
|
+
|
104
|
+
def exclusive_comparator(direction)
|
105
|
+
direction == :asc ? '>' : '<'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/stagehand/engine.rb
CHANGED
@@ -14,8 +14,9 @@ module Stagehand
|
|
14
14
|
require "stagehand/database"
|
15
15
|
require "stagehand/controller_extensions"
|
16
16
|
require "stagehand/staging"
|
17
|
-
require "stagehand/schema"
|
18
17
|
require "stagehand/production"
|
18
|
+
require "stagehand/schema"
|
19
|
+
require 'stagehand/auditor'
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
data/lib/stagehand/production.rb
CHANGED
@@ -5,22 +5,22 @@ module Stagehand
|
|
5
5
|
extend self
|
6
6
|
|
7
7
|
# Outputs a symbol representing the status of the staging record as it exists in the production database
|
8
|
-
def status(staging_record)
|
9
|
-
if !exists?(staging_record)
|
8
|
+
def status(staging_record, table_name = nil)
|
9
|
+
if !exists?(staging_record, table_name)
|
10
10
|
:new
|
11
|
-
elsif modified?(staging_record)
|
11
|
+
elsif modified?(staging_record, table_name)
|
12
12
|
:modified
|
13
13
|
else
|
14
14
|
:not_modified
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def save(staging_record)
|
19
|
-
attributes = staging_record_attributes(staging_record)
|
18
|
+
def save(staging_record, table_name = nil)
|
19
|
+
attributes = staging_record_attributes(staging_record, table_name)
|
20
20
|
|
21
21
|
return unless attributes.present?
|
22
22
|
|
23
|
-
is_new = lookup(staging_record).update_all(attributes).zero?
|
23
|
+
is_new = lookup(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|
|
@@ -40,8 +40,8 @@ module Stagehand
|
|
40
40
|
# Returns true if the staging record's attributes are different from the production record's attributes
|
41
41
|
# Returns true if the staging_record does not exist on production
|
42
42
|
# Returns false if the staging record is identical to the production record
|
43
|
-
def modified?(staging_record)
|
44
|
-
production_record_attributes(staging_record) != staging_record_attributes(staging_record)
|
43
|
+
def modified?(staging_record, table_name = nil)
|
44
|
+
production_record_attributes(staging_record, table_name) != staging_record_attributes(staging_record, table_name)
|
45
45
|
end
|
46
46
|
|
47
47
|
# Returns a scope that limits results any occurrences of the specified record.
|
@@ -59,8 +59,8 @@ module Stagehand
|
|
59
59
|
Record.table_name = table_name
|
60
60
|
end
|
61
61
|
|
62
|
-
def production_record_attributes(staging_record)
|
63
|
-
Record.connection.select_one(lookup(staging_record))
|
62
|
+
def production_record_attributes(staging_record, table_name = nil)
|
63
|
+
Record.connection.select_one(lookup(staging_record, table_name))
|
64
64
|
end
|
65
65
|
|
66
66
|
def staging_record_attributes(staging_record, table_name = nil)
|
@@ -33,8 +33,15 @@ module Stagehand
|
|
33
33
|
keys = Array.wrap(object).collect {|entry| Stagehand::Key.generate(entry) }.compact
|
34
34
|
sql = []
|
35
35
|
interpolates = []
|
36
|
+
groups = keys.group_by(&:first)
|
36
37
|
|
37
|
-
|
38
|
+
# If passed control operation commit entries, ensure they are returned since their keys match the CommitEntry's primary key
|
39
|
+
if commit_entry_group = groups.delete(CommitEntry.table_name)
|
40
|
+
sql << 'id IN (?)'
|
41
|
+
interpolates << commit_entry_group.collect(&:last)
|
42
|
+
end
|
43
|
+
|
44
|
+
groups.each do |table_name, keys|
|
38
45
|
sql << "(table_name = ? AND record_id IN (?))"
|
39
46
|
interpolates << table_name
|
40
47
|
interpolates << keys.collect(&:last)
|
@@ -18,13 +18,27 @@ module Stagehand
|
|
18
18
|
scope = autosyncable_entries.limit(1000)
|
19
19
|
|
20
20
|
loop do
|
21
|
-
|
21
|
+
Rails.logger.info "Synced #{sync_entries(scope.reload)} entries"
|
22
22
|
sleep(delay) if delay
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
def sync
|
27
|
-
sync_entries(autosyncable_entries.limit(
|
26
|
+
def sync(limit = nil)
|
27
|
+
sync_entries(autosyncable_entries.limit(limit))
|
28
|
+
end
|
29
|
+
|
30
|
+
def sync_all
|
31
|
+
loop do
|
32
|
+
entries = CommitEntry.order(:id => :desc).limit(1000).to_a
|
33
|
+
break unless entries.present?
|
34
|
+
|
35
|
+
latest_entries = entries.uniq(&:key)
|
36
|
+
sync_entries(latest_entries)
|
37
|
+
Rails.logger.info "Synced #{latest_entries.count} entries"
|
38
|
+
|
39
|
+
deleted_count = CommitEntry.matching(latest_entries).delete_all
|
40
|
+
Rails.logger.info "Removed #{deleted_count - latest_entries.count} stale entries"
|
41
|
+
end
|
28
42
|
end
|
29
43
|
|
30
44
|
# Copies all the affected records from the staging database to the production database
|
@@ -46,7 +60,7 @@ module Stagehand
|
|
46
60
|
|
47
61
|
ActiveRecord::Base.transaction do
|
48
62
|
entries.each do |entry|
|
49
|
-
Rails.logger.info "Synchronizing #{entry.table_name} #{entry.record_id}"
|
63
|
+
Rails.logger.info "Synchronizing #{entry.table_name} #{entry.record_id}" if entry.content_operation?
|
50
64
|
if entry.delete_operation?
|
51
65
|
Stagehand::Production.delete(entry)
|
52
66
|
elsif entry.save_operation?
|
data/lib/stagehand/staging.rb
CHANGED
data/lib/stagehand/version.rb
CHANGED
@@ -4,6 +4,16 @@ namespace :stagehand do
|
|
4
4
|
Stagehand::Staging::Synchronizer.auto_sync(args[:delay] ||= 5.seconds)
|
5
5
|
end
|
6
6
|
|
7
|
+
desc "Syncs records that don't need confirmation to production"
|
8
|
+
task :sync, [:limit] => :environment do |t, args|
|
9
|
+
Stagehand::Staging::Synchronizer.sync(args[:limit])
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Syncs all records to production, including those that require confirmation"
|
13
|
+
task :sync_all, :environment do
|
14
|
+
Stagehand::Staging::Synchronizer.sync_all
|
15
|
+
end
|
16
|
+
|
7
17
|
desc "Migrate both databases used by stagehand"
|
8
18
|
task :migrate => :environment do
|
9
19
|
[Rails.configuration.x.stagehand.staging_connection_name,
|
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.3.
|
4
|
+
version: 0.3.1
|
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-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -67,6 +67,7 @@ files:
|
|
67
67
|
- Rakefile
|
68
68
|
- lib/culturecode_stagehand.rb
|
69
69
|
- lib/stagehand.rb
|
70
|
+
- lib/stagehand/auditor.rb
|
70
71
|
- lib/stagehand/cache.rb
|
71
72
|
- lib/stagehand/configuration.rb
|
72
73
|
- lib/stagehand/controller_extensions.rb
|
@@ -78,7 +79,6 @@ files:
|
|
78
79
|
- lib/stagehand/schema.rb
|
79
80
|
- lib/stagehand/schema/statements.rb
|
80
81
|
- lib/stagehand/staging.rb
|
81
|
-
- lib/stagehand/staging/auditor.rb
|
82
82
|
- lib/stagehand/staging/checklist.rb
|
83
83
|
- lib/stagehand/staging/commit.rb
|
84
84
|
- lib/stagehand/staging/commit_entry.rb
|
@@ -1,64 +0,0 @@
|
|
1
|
-
module Stagehand
|
2
|
-
module Staging
|
3
|
-
module Auditor
|
4
|
-
extend self
|
5
|
-
|
6
|
-
def incomplete_commits
|
7
|
-
incomplete = []
|
8
|
-
|
9
|
-
incomplete_start_operations.each do |start_operation|
|
10
|
-
entries = records_until_match(start_operation, :asc, :operation => CommitEntry::START_OPERATION).to_a
|
11
|
-
incomplete << [start_operation.id, entries]
|
12
|
-
end
|
13
|
-
|
14
|
-
incomplete_end_operations.each do |end_operation|
|
15
|
-
entries = records_through_match(end_operation, :desc, :operation => CommitEntry::START_OPERATION).to_a
|
16
|
-
incomplete << [entries.last.id, entries]
|
17
|
-
end
|
18
|
-
|
19
|
-
return incomplete.to_h
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
# Incomplete End Operation that are not the last entry in their session
|
25
|
-
def incomplete_end_operations
|
26
|
-
last_entry_per_session = CommitEntry.group(:session).select('MAX(id) AS id')
|
27
|
-
return CommitEntry.uncontained.end_operations.where.not(:id => last_entry_per_session)
|
28
|
-
end
|
29
|
-
|
30
|
-
# Incomplete Start on the same session as a subsequent start operation
|
31
|
-
def incomplete_start_operations
|
32
|
-
last_start_entry_per_session = CommitEntry.start_operations.group(:session).select('MAX(id) AS id')
|
33
|
-
return CommitEntry.uncontained.start_operations.where.not(:id => last_start_entry_per_session)
|
34
|
-
end
|
35
|
-
|
36
|
-
def records_until_match(start_entry, direction, match_attributes)
|
37
|
-
records_through_match(start_entry, direction, match_attributes)[0..-2]
|
38
|
-
end
|
39
|
-
|
40
|
-
def records_through_match(start_entry, direction, match_attributes)
|
41
|
-
last_entry = next_match(start_entry, direction, match_attributes)
|
42
|
-
return records_from(start_entry, direction).where.not("id #{exclusive_comparator(direction)} ?", last_entry)
|
43
|
-
end
|
44
|
-
|
45
|
-
def next_match(start_entry, direction, match_attributes)
|
46
|
-
records_from(start_entry, direction).where.not(:id => start_entry.id).where(match_attributes).first
|
47
|
-
end
|
48
|
-
|
49
|
-
def records_from(start_entry, direction)
|
50
|
-
scope = CommitEntry.where(:session => start_entry.session).where("id #{comparator(direction)} ?", start_entry.id)
|
51
|
-
scope = scope.reverse_order if direction == :desc
|
52
|
-
return scope
|
53
|
-
end
|
54
|
-
|
55
|
-
def comparator(direction)
|
56
|
-
exclusive_comparator(direction) + '='
|
57
|
-
end
|
58
|
-
|
59
|
-
def exclusive_comparator(direction)
|
60
|
-
direction == :asc ? '>' : '<'
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|