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