culturecode_stagehand 0.15.1 → 1.0.3
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/Rakefile +9 -1
- data/lib/stagehand.rb +2 -1
- data/lib/stagehand/active_record_extensions.rb +59 -3
- data/lib/stagehand/connection_adapter_extensions.rb +5 -9
- data/lib/stagehand/controller_extensions.rb +2 -2
- data/lib/stagehand/database.rb +73 -25
- data/lib/stagehand/engine.rb +13 -12
- data/lib/stagehand/schema.rb +1 -1
- data/lib/stagehand/schema_extensions.rb +10 -0
- data/lib/stagehand/staging/checklist.rb +10 -8
- data/lib/stagehand/staging/commit_entry.rb +28 -17
- data/lib/stagehand/staging/synchronizer.rb +5 -2
- data/lib/stagehand/version.rb +1 -1
- metadata +56 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 778925f3540847fcf089ab3ffb65d6993362c59e
|
4
|
+
data.tar.gz: 770602a0719fef798f82744b00e9c60dc33f6c24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd85561ed53df15f0180ef0a42eaa17f01e6c904e6208aec47b87b9ba3fdd661285c1bb88e8d6d16916df38247ff399d9d8e371b34761526bd877ce724d65b22
|
7
|
+
data.tar.gz: 1e0a9fe56efcf5fe0a65183cbc3d798c629c814d6be31bf3aa4fa637821fbe4e8d9c037c1fa302372da03910f61d8066afd1dce047e218b1be059ced4afc7486
|
data/Rakefile
CHANGED
@@ -4,9 +4,17 @@ rescue LoadError
|
|
4
4
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
5
|
end
|
6
6
|
|
7
|
-
APP_RAKEFILE = File.expand_path("../spec/
|
7
|
+
APP_RAKEFILE = File.expand_path("../spec/internal/Rakefile", __FILE__)
|
8
8
|
load 'rails/tasks/engine.rake'
|
9
9
|
|
10
10
|
load 'rails/tasks/statistics.rake'
|
11
11
|
|
12
12
|
Bundler::GemHelper.install_tasks
|
13
|
+
|
14
|
+
|
15
|
+
# Add Rspec tasks
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
|
18
|
+
RSpec::Core::RakeTask.new(:spec)
|
19
|
+
|
20
|
+
task :default => :spec
|
data/lib/stagehand.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
ActiveRecord::Base.class_eval do
|
2
4
|
# SYNC CALLBACKS
|
3
|
-
|
5
|
+
([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |klass|
|
6
|
+
klass.define_model_callbacks :sync, :sync_as_subject, :sync_as_affected
|
7
|
+
end
|
4
8
|
|
5
9
|
# SYNC STATUS
|
6
10
|
def self.inherited(subclass)
|
@@ -9,18 +13,70 @@ ActiveRecord::Base.class_eval do
|
|
9
13
|
subclass.class_eval do
|
10
14
|
has_one :stagehand_unsynced_indicator,
|
11
15
|
lambda { where(:stagehand_commit_entries => {:table_name => subclass.table_name}).readonly },
|
12
|
-
:class_name => Stagehand::Staging::CommitEntry,
|
16
|
+
:class_name => 'Stagehand::Staging::CommitEntry',
|
17
|
+
:foreign_key => :record_id
|
18
|
+
|
19
|
+
has_one :stagehand_unsynced_commit_indicator,
|
20
|
+
lambda { where(:stagehand_commit_entries => {:table_name => subclass.table_name}).where.not(commit_id: nil).readonly },
|
21
|
+
:class_name => 'Stagehand::Staging::CommitEntry',
|
13
22
|
:foreign_key => :record_id
|
14
23
|
|
15
24
|
def synced?
|
16
25
|
stagehand_unsynced_indicator.blank?
|
17
26
|
end
|
27
|
+
|
28
|
+
def synced_all_commits?
|
29
|
+
stagehand_unsynced_commit_indicator.blank?
|
30
|
+
end
|
18
31
|
end
|
19
32
|
end
|
20
33
|
|
21
34
|
# SCHEMA
|
22
35
|
delegate :has_stagehand?, to: :class
|
23
36
|
def self.has_stagehand?
|
24
|
-
@has_stagehand
|
37
|
+
@has_stagehand = Stagehand::Schema.has_stagehand?(table_name) unless defined?(@has_stagehand)
|
38
|
+
return @has_stagehand
|
39
|
+
end
|
40
|
+
|
41
|
+
# MULTITHREADED CONNECTION HANDLING
|
42
|
+
|
43
|
+
# The original implementation of remove_connection uses @connection_specification_name, which is shared across Threads.
|
44
|
+
# We need to pass in the connection that model in the current thread is using if we call remove_connection.
|
45
|
+
def self.remove_connection(name = StagehandConnectionMap.get(self))
|
46
|
+
StagehandConnectionMap.set(self, nil)
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.connection_specification_name=(connection_name)
|
51
|
+
# ActiveRecord sets the connection pool to 'primary' by default, so we want to reuse that connection for staging
|
52
|
+
# in order to avoid using a different connection pool after our first swap back to the staging connection.
|
53
|
+
connection_name == 'primary' if connection_name == Stagehand::Configuration.staging_connection_name
|
54
|
+
|
55
|
+
StagehandConnectionMap.set(self, connection_name)
|
56
|
+
|
57
|
+
# We want to keep track of the @connection_specification_name as a fallback shared across threads in case we
|
58
|
+
# haven't set the connection on more than one thread.
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.connection_specification_name
|
63
|
+
StagehandConnectionMap.get(self) || super
|
64
|
+
end
|
65
|
+
|
66
|
+
# Keep track of the current connection name per-model, per-thread so multithreaded webservers don't overwrite it
|
67
|
+
module StagehandConnectionMap
|
68
|
+
def self.set(klass, connection_name)
|
69
|
+
currentMap[klass.name] = connection_name
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.get(klass)
|
73
|
+
currentMap[klass.name]
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.currentMap
|
77
|
+
map = Thread.current.thread_variable_get('StagehandConnectionMap')
|
78
|
+
map = Thread.current.thread_variable_set('StagehandConnectionMap', {}) unless map
|
79
|
+
return map
|
80
|
+
end
|
25
81
|
end
|
26
82
|
end
|
@@ -1,11 +1,7 @@
|
|
1
1
|
module Stagehand
|
2
2
|
module Connection
|
3
3
|
def self.with_production_writes(model, &block)
|
4
|
-
|
5
|
-
model.connection.readonly!(false)
|
6
|
-
return block.call
|
7
|
-
ensure
|
8
|
-
model.connection.readonly!(state)
|
4
|
+
model.connection.allow_writes(&block)
|
9
5
|
end
|
10
6
|
|
11
7
|
module AdapterExtensions
|
@@ -31,7 +27,7 @@ module Stagehand
|
|
31
27
|
|
32
28
|
def allow_writes(&block)
|
33
29
|
state = readonly?
|
34
|
-
readonly!(
|
30
|
+
readonly!(false)
|
35
31
|
return block.call
|
36
32
|
ensure
|
37
33
|
readonly!(state)
|
@@ -48,7 +44,7 @@ module Stagehand
|
|
48
44
|
private
|
49
45
|
|
50
46
|
def update_readonly_state
|
51
|
-
readonly! unless Configuration.single_connection? || @config[:database]
|
47
|
+
readonly! unless Configuration.single_connection? || @config[:database] == Database.staging_database_name
|
52
48
|
end
|
53
49
|
|
54
50
|
def clear_readonly_state
|
@@ -59,9 +55,9 @@ module Stagehand
|
|
59
55
|
if !readonly?
|
60
56
|
return
|
61
57
|
elsif Configuration.allow_unsynced_production_writes?
|
62
|
-
Rails.logger.warn "Writing directly to
|
58
|
+
Rails.logger.warn "Writing directly to #{@config[:database]} database using readonly connection"
|
63
59
|
else
|
64
|
-
raise(UnsyncedProductionWrite, "Attempted to write directly to
|
60
|
+
raise(UnsyncedProductionWrite, "Attempted to write directly to #{@config[:database]} database using readonly connection")
|
65
61
|
end
|
66
62
|
end
|
67
63
|
end
|
@@ -4,12 +4,12 @@ module Stagehand
|
|
4
4
|
|
5
5
|
class_methods do
|
6
6
|
def use_staging_database(options = {})
|
7
|
-
|
7
|
+
skip_around_action :use_production_database, raise: false, **options
|
8
8
|
prepend_around_action :use_staging_database, options
|
9
9
|
end
|
10
10
|
|
11
11
|
def use_production_database(options = {})
|
12
|
-
|
12
|
+
skip_around_action :use_staging_database, raise: false, **options
|
13
13
|
prepend_around_action :use_production_database, options
|
14
14
|
end
|
15
15
|
end
|
data/lib/stagehand/database.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
module Stagehand
|
2
4
|
module Database
|
3
5
|
extend self
|
4
6
|
|
5
|
-
@@connection_name_stack = [Rails.env.to_sym]
|
6
|
-
|
7
7
|
def each(&block)
|
8
8
|
with_production_connection(&block) unless Configuration.single_connection?
|
9
9
|
with_staging_connection(&block)
|
@@ -49,24 +49,16 @@ module Stagehand
|
|
49
49
|
with_connection(Configuration.production_connection_name, &block)
|
50
50
|
end
|
51
51
|
|
52
|
-
def with_connection(connection_name)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
Rails.logger.debug "Connecting to #{current_connection_name}"
|
58
|
-
connect_to(current_connection_name)
|
52
|
+
def with_connection(connection_name, &block)
|
53
|
+
if current_connection_name != connection_name.to_sym
|
54
|
+
Rails.logger.debug "Connecting to #{connection_name}"
|
55
|
+
output = swap_connection(connection_name, &block)
|
56
|
+
Rails.logger.debug "Restoring connection to #{current_connection_name}"
|
59
57
|
else
|
60
58
|
Rails.logger.debug "Already connected to #{connection_name}"
|
59
|
+
output = yield connection_name
|
61
60
|
end
|
62
|
-
|
63
|
-
yield connection_name
|
64
|
-
ensure
|
65
|
-
if different
|
66
|
-
@@connection_name_stack.pop
|
67
|
-
Rails.logger.debug "Restoring connection to #{current_connection_name}"
|
68
|
-
connect_to(current_connection_name)
|
69
|
-
end
|
61
|
+
return output
|
70
62
|
end
|
71
63
|
|
72
64
|
def transaction
|
@@ -84,12 +76,27 @@ module Stagehand
|
|
84
76
|
|
85
77
|
private
|
86
78
|
|
87
|
-
def
|
88
|
-
|
79
|
+
def swap_connection(connection_name)
|
80
|
+
raise ConnectionSwapDuringTransaction unless safe_to_swap_connection?
|
81
|
+
|
82
|
+
cache = ActiveRecord::Base.connection_pool.query_cache_enabled
|
83
|
+
ConnectionStack.push(connection_name.to_sym)
|
84
|
+
ActiveRecord::Base.connection_specification_name = current_connection_name
|
85
|
+
ActiveRecord::Base.connection_pool.enable_query_cache! if cache
|
86
|
+
|
87
|
+
yield connection_name
|
88
|
+
ensure
|
89
|
+
ConnectionStack.pop
|
90
|
+
ActiveRecord::Base.connection_specification_name = current_connection_name
|
91
|
+
ActiveRecord::Base.connection_pool.enable_query_cache! if cache
|
92
|
+
end
|
93
|
+
|
94
|
+
def safe_to_swap_connection?
|
95
|
+
ActiveRecord::Base.connection.open_transactions == 0
|
89
96
|
end
|
90
97
|
|
91
98
|
def current_connection_name
|
92
|
-
|
99
|
+
ConnectionStack.last
|
93
100
|
end
|
94
101
|
|
95
102
|
def database_name(connection_name)
|
@@ -102,24 +109,65 @@ module Stagehand
|
|
102
109
|
|
103
110
|
# CLASSES
|
104
111
|
|
105
|
-
class
|
112
|
+
class Probe < ActiveRecord::Base
|
113
|
+
self.abstract_class = true
|
114
|
+
|
115
|
+
# We fake the class name so we can create a connection pool with the desired connection name instead of the name of the class
|
116
|
+
def self.init_connection(connection_name)
|
117
|
+
@probe_name = connection_name
|
118
|
+
establish_connection(connection_name)
|
119
|
+
ensure
|
120
|
+
@probe_name = nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.name
|
124
|
+
@probe_name || super
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class StagingProbe < Probe
|
106
129
|
self.abstract_class = true
|
107
130
|
|
108
131
|
def self.init_connection
|
109
|
-
|
132
|
+
super(Configuration.staging_connection_name)
|
110
133
|
end
|
111
134
|
|
112
135
|
init_connection
|
113
136
|
end
|
114
137
|
|
115
|
-
class ProductionProbe <
|
138
|
+
class ProductionProbe < Probe
|
116
139
|
self.abstract_class = true
|
117
140
|
|
118
141
|
def self.init_connection
|
119
|
-
|
142
|
+
super(Configuration.production_connection_name)
|
120
143
|
end
|
121
144
|
|
122
|
-
init_connection
|
145
|
+
init_connection unless Configuration.single_connection?
|
146
|
+
end
|
147
|
+
|
148
|
+
# Threadsafe tracking of the connection stack
|
149
|
+
module ConnectionStack
|
150
|
+
@@connection_name_stack = Hash.new { |h,k| h[k] = [ Rails.env.to_sym ] }
|
151
|
+
|
152
|
+
def self.push(connection_name)
|
153
|
+
current_stack.push connection_name
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.pop
|
157
|
+
current_stack.pop
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.last
|
161
|
+
current_stack.last
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.current_stack
|
165
|
+
@@connection_name_stack[Thread.current.object_id]
|
166
|
+
end
|
123
167
|
end
|
124
168
|
end
|
169
|
+
|
170
|
+
# EXCEPTIONS
|
171
|
+
|
172
|
+
class ConnectionSwapDuringTransaction < StandardError; end
|
125
173
|
end
|
data/lib/stagehand/engine.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'stagehand/configuration'
|
2
2
|
|
3
3
|
module Stagehand
|
4
4
|
class Engine < ::Rails::Engine
|
@@ -8,17 +8,18 @@ module Stagehand
|
|
8
8
|
g.test_framework :rspec
|
9
9
|
end
|
10
10
|
|
11
|
-
# These require the rails application to be
|
12
|
-
initializer
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
11
|
+
# These require the rails application to be initialized because configuration variables are used
|
12
|
+
initializer 'stagehand.load_modules' do
|
13
|
+
require 'stagehand/cache'
|
14
|
+
require 'stagehand/key'
|
15
|
+
require 'stagehand/database'
|
16
|
+
require 'stagehand/connection_adapter_extensions'
|
17
|
+
require 'stagehand/controller_extensions'
|
18
|
+
require 'stagehand/active_record_extensions'
|
19
|
+
require 'stagehand/schema_extensions'
|
20
|
+
require 'stagehand/staging'
|
21
|
+
require 'stagehand/production'
|
22
|
+
require 'stagehand/schema'
|
22
23
|
require 'stagehand/auditor'
|
23
24
|
end
|
24
25
|
end
|
data/lib/stagehand/schema.rb
CHANGED
@@ -4,7 +4,7 @@ module Stagehand
|
|
4
4
|
module Schema
|
5
5
|
extend self
|
6
6
|
|
7
|
-
UNTRACKED_TABLES = ['schema_migrations', Stagehand::Staging::CommitEntry.table_name]
|
7
|
+
UNTRACKED_TABLES = ['ar_internal_metadata', 'schema_migrations', Stagehand::Staging::CommitEntry.table_name]
|
8
8
|
|
9
9
|
def init_stagehand!(options = {})
|
10
10
|
ActiveRecord::Schema.define do
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Stagehand
|
2
|
+
module SchemaExtensions
|
3
|
+
def define(*)
|
4
|
+
# Allow production writes during Schema.define to allow Rails to write to ar_internal_metadata table
|
5
|
+
Stagehand::Connection.with_production_writes(ActiveRecord::Base) { super }
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
ActiveRecord::Schema.prepend(Stagehand::SchemaExtensions)
|
@@ -55,10 +55,10 @@ module Stagehand
|
|
55
55
|
end
|
56
56
|
|
57
57
|
# Returns a list of entries that only includes a single entry for each record.
|
58
|
-
# The
|
59
|
-
def self.compact_entries(entries)
|
58
|
+
# The entries are prioritized by the list of operations as given by `:priority`.
|
59
|
+
def self.compact_entries(entries, priority: [:delete, :update, :insert])
|
60
60
|
compact_entries = group_entries(entries)
|
61
|
-
compact_entries = compact_entries
|
61
|
+
compact_entries = compact_entries.values_at(*priority).flatten
|
62
62
|
compact_entries.uniq!(&:key)
|
63
63
|
|
64
64
|
return compact_entries
|
@@ -74,7 +74,7 @@ module Stagehand
|
|
74
74
|
|
75
75
|
def self.preload_records(entries)
|
76
76
|
entries.group_by(&:table_name).each do |table_name, group_entries|
|
77
|
-
klass = CommitEntry.
|
77
|
+
klass = CommitEntry.infer_base_class(table_name)
|
78
78
|
records = klass.where(:id => group_entries.collect(&:record_id))
|
79
79
|
records = records.includes(associated_associations(klass))
|
80
80
|
records_by_id = records.collect {|r| [r.id, r] }.to_h
|
@@ -129,7 +129,7 @@ module Stagehand
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def syncing_entries
|
132
|
-
cache(:syncing_entries) { self.class.compact_entries(affected_entries) }
|
132
|
+
cache(:syncing_entries) { self.class.compact_entries(affected_entries, priority: Synchronizer::ENTRY_SYNC_ORDER) }
|
133
133
|
end
|
134
134
|
|
135
135
|
def affected_records
|
@@ -138,11 +138,13 @@ module Stagehand
|
|
138
138
|
|
139
139
|
def affected_entries
|
140
140
|
cache(:affected_entries) do
|
141
|
-
|
141
|
+
from_subject = subject_entries
|
142
|
+
from_subject |= CommitEntry.where(commit_id: subject_entries.select(:commit_id))
|
143
|
+
related = self.class.related_entries(from_subject, @relation_filter)
|
142
144
|
associated = self.class.associated_records(related, @association_filter)
|
143
145
|
associated_related = self.class.related_entries(associated, @relation_filter)
|
144
146
|
|
145
|
-
(related + associated_related).uniq
|
147
|
+
(from_subject + related + associated_related).uniq
|
146
148
|
end
|
147
149
|
end
|
148
150
|
|
@@ -172,7 +174,7 @@ module Stagehand
|
|
172
174
|
end.collect(&:id)
|
173
175
|
entries.reject! {|entry| staging_record_start_operation_ids.include?(entry.commit_id) }
|
174
176
|
|
175
|
-
entries = self.class.compact_entries(entries)
|
177
|
+
entries = self.class.compact_entries(entries, priority: [:delete, :insert, :update])
|
176
178
|
entries = filter_entries(entries)
|
177
179
|
entries = self.class.group_entries(entries)
|
178
180
|
end
|
@@ -60,16 +60,10 @@ module Stagehand
|
|
60
60
|
return keys.present? ? where(sql.join(' OR '), *interpolates) : none
|
61
61
|
end
|
62
62
|
|
63
|
-
def self.
|
63
|
+
def self.infer_base_class(table_name)
|
64
64
|
classes = ActiveRecord::Base.descendants.select {|klass| klass.table_name == table_name }
|
65
65
|
classes.delete(Stagehand::Production::Record)
|
66
|
-
|
67
|
-
|
68
|
-
if record_id && record = root_class.find_by_id(record_id)
|
69
|
-
klass = record.class
|
70
|
-
end
|
71
|
-
|
72
|
-
return klass || root_class
|
66
|
+
return classes.first || table_name.classify.constantize.base_class # Try loading the class if it isn't loaded yet
|
73
67
|
rescue NameError
|
74
68
|
raise(IndeterminateRecordClass, "Can't determine class from table name: #{table_name}")
|
75
69
|
end
|
@@ -122,22 +116,37 @@ module Stagehand
|
|
122
116
|
end
|
123
117
|
|
124
118
|
def record_class
|
125
|
-
@record_class ||=
|
119
|
+
@record_class ||= infer_class
|
126
120
|
rescue IndeterminateRecordClass
|
127
121
|
@record_class ||= self.class.build_missing_model(table_name)
|
128
122
|
end
|
129
123
|
|
124
|
+
def production_record
|
125
|
+
@production_record = Stagehand::Production.find(record_id, table_name) unless defined?(@production_record)
|
126
|
+
return @production_record
|
127
|
+
end
|
128
|
+
|
130
129
|
private
|
131
130
|
|
131
|
+
def infer_class
|
132
|
+
klass = self.class.infer_base_class(table_name)
|
133
|
+
klass = infer_sti_class(klass) || infer_production_sti_class(klass) || klass if record_id
|
134
|
+
return klass
|
135
|
+
end
|
136
|
+
|
137
|
+
def infer_sti_class(root_class)
|
138
|
+
root_class.find_by_id(record_id)&.class
|
139
|
+
end
|
140
|
+
|
141
|
+
def infer_production_sti_class(root_class)
|
142
|
+
production_record[root_class.inheritance_column]&.constantize if production_record
|
143
|
+
end
|
144
|
+
|
132
145
|
def build_deleted_record
|
133
|
-
production_record = Stagehand::Production.find(record_id, table_name)
|
134
146
|
return unless production_record
|
135
|
-
|
136
|
-
deleted_record = record_class.new(production_record.attributes)
|
147
|
+
deleted_record = production_record.becomes(record_class)
|
137
148
|
deleted_record.readonly!
|
138
|
-
deleted_record.
|
139
|
-
deleted_record.instance_variable_set(:@destroyed, true)
|
140
|
-
|
149
|
+
deleted_record.singleton_class.include(DeletedRecord)
|
141
150
|
return deleted_record
|
142
151
|
end
|
143
152
|
|
@@ -149,11 +158,13 @@ module Stagehand
|
|
149
158
|
end
|
150
159
|
end
|
151
160
|
|
152
|
-
#
|
161
|
+
# UTILITY MODULES
|
153
162
|
|
154
|
-
module DummyClass; end
|
163
|
+
module DummyClass; end # A namespace for dummy classes of records with an IndeterminateRecordClass
|
164
|
+
module DeletedRecord; end # Serves as a way of tagging an instance to see if it is_a?(Stagehand::DeletedRecord)
|
155
165
|
|
156
166
|
# EXCEPTIONS
|
167
|
+
|
157
168
|
class IndeterminateRecordClass < StandardError; end
|
158
169
|
class MissingTable < StandardError; end
|
159
170
|
end
|
@@ -6,6 +6,8 @@ module Stagehand
|
|
6
6
|
|
7
7
|
BATCH_SIZE = 1000
|
8
8
|
SESSION_BATCH_SIZE = 30
|
9
|
+
ENTRY_SYNC_ORDER = [:delete, :update, :insert].freeze
|
10
|
+
ENTRY_SYNC_ORDER_SQL = ActiveRecord::Base.send(:sanitize_sql_for_order, ['FIELD(operation, ?), id DESC', ENTRY_SYNC_ORDER]).freeze
|
9
11
|
|
10
12
|
# Immediately attempt to sync the changes from the block if possible
|
11
13
|
# The block is wrapped in a transaction to prevent changes to records while being synced
|
@@ -50,7 +52,7 @@ module Stagehand
|
|
50
52
|
|
51
53
|
def sync_all
|
52
54
|
loop do
|
53
|
-
entries = CommitEntry.order(
|
55
|
+
entries = CommitEntry.order(ENTRY_SYNC_ORDER_SQL).limit(BATCH_SIZE).to_a
|
54
56
|
break unless entries.present?
|
55
57
|
|
56
58
|
latest_entries = entries.uniq(&:key)
|
@@ -87,7 +89,8 @@ module Stagehand
|
|
87
89
|
# Returns commit entries in ID descending order
|
88
90
|
def iterate_autosyncable_entries(&block)
|
89
91
|
current = CommitEntry.maximum(:id).to_i
|
90
|
-
|
92
|
+
|
93
|
+
while entries = autosyncable_entries("id <= #{current}").limit(BATCH_SIZE).order(ENTRY_SYNC_ORDER_SQL).to_a.presence do
|
91
94
|
with_confirmed_autosyncability(entries.uniq(&:key), &block)
|
92
95
|
current = entries.last.try(:id).to_i - 1
|
93
96
|
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: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicholas Jakobsen
|
@@ -9,22 +9,28 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2019-01-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - "
|
18
|
+
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: '4.2'
|
21
|
+
- - "<"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '5.2'
|
21
24
|
type: :runtime
|
22
25
|
prerelease: false
|
23
26
|
version_requirements: !ruby/object:Gem::Requirement
|
24
27
|
requirements:
|
25
|
-
- - "
|
28
|
+
- - ">="
|
26
29
|
- !ruby/object:Gem::Version
|
27
30
|
version: '4.2'
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.2'
|
28
34
|
- !ruby/object:Gem::Dependency
|
29
35
|
name: mysql2
|
30
36
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,20 +59,62 @@ dependencies:
|
|
53
59
|
- - ">="
|
54
60
|
- !ruby/object:Gem::Version
|
55
61
|
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: mysql2
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.4.0
|
69
|
+
type: :development
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.4.0
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: combustion
|
78
|
+
requirement: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.9.0
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.9.0
|
56
90
|
- !ruby/object:Gem::Dependency
|
57
91
|
name: rspec-rails
|
58
92
|
requirement: !ruby/object:Gem::Requirement
|
59
93
|
requirements:
|
60
94
|
- - "~>"
|
61
95
|
- !ruby/object:Gem::Version
|
62
|
-
version: '3.
|
96
|
+
version: '3.7'
|
63
97
|
type: :development
|
64
98
|
prerelease: false
|
65
99
|
version_requirements: !ruby/object:Gem::Requirement
|
66
100
|
requirements:
|
67
101
|
- - "~>"
|
68
102
|
- !ruby/object:Gem::Version
|
69
|
-
version: '3.
|
103
|
+
version: '3.7'
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: database_cleaner
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
70
118
|
description: Simplify the management of a sandbox database that can sync content to
|
71
119
|
a production database. Content changes can be bundled to allow partial syncs of
|
72
120
|
the database.
|
@@ -95,6 +143,7 @@ files:
|
|
95
143
|
- lib/stagehand/production/controller.rb
|
96
144
|
- lib/stagehand/schema.rb
|
97
145
|
- lib/stagehand/schema/statements.rb
|
146
|
+
- lib/stagehand/schema_extensions.rb
|
98
147
|
- lib/stagehand/staging.rb
|
99
148
|
- lib/stagehand/staging/checklist.rb
|
100
149
|
- lib/stagehand/staging/commit.rb
|
@@ -124,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
173
|
version: '0'
|
125
174
|
requirements: []
|
126
175
|
rubyforge_project:
|
127
|
-
rubygems_version: 2.6.
|
176
|
+
rubygems_version: 2.6.14
|
128
177
|
signing_key:
|
129
178
|
specification_version: 4
|
130
179
|
summary: Simplify the management of a sandbox database that can sync content to a
|