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 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