culturecode_stagehand 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|