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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db833ca765b28d4f8c2ee6700ffdd3e450dff705
4
- data.tar.gz: ed31a76fa1c17d8edacdc595a2d7353c0c0720b2
3
+ metadata.gz: 88258f8ab5b38d62a941024ddeef50d3e66ac13b
4
+ data.tar.gz: 9f1571c1823366b2a5b576d9e213a7585713e883
5
5
  SHA512:
6
- metadata.gz: 2cf9ce58d98f5c1b962036280f0ccb9f0da9ac3bb881c7f08f2689f944cb7b5e255062fb018b79b630f392258b39007316b636c90972aef4cc68cffb5e79946a
7
- data.tar.gz: b0dea71a1f093f7f78fc836bc427a20a3e7b777fbfd5d6456909be62ea1b2be1c078d695ef0abbc8831cc63b7d64081ce8a1cb2901fd60ca2b94f69e8898a830
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
@@ -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
@@ -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
- keys.group_by(&:first).each do |table_name, keys|
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
- puts "Synced #{sync_entries(scope.reload)} entries"
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(1000))
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?
@@ -4,7 +4,6 @@ require 'stagehand/staging/checklist'
4
4
  require 'stagehand/staging/controller'
5
5
  require 'stagehand/staging/model'
6
6
  require 'stagehand/staging/synchronizer'
7
- require 'stagehand/staging/auditor'
8
7
 
9
8
  module Stagehand
10
9
  module Staging
@@ -1,3 +1,3 @@
1
1
  module Stagehand
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -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.0
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-14 00:00:00.000000000 Z
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