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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5fdc23b385dbb657b54cb029e4714657b4aa3e69
4
- data.tar.gz: bdbd449b63397e6a68e223616d1ee6f7ce737c58
3
+ metadata.gz: 778925f3540847fcf089ab3ffb65d6993362c59e
4
+ data.tar.gz: 770602a0719fef798f82744b00e9c60dc33f6c24
5
5
  SHA512:
6
- metadata.gz: 87fb81e17c7fb2837b9217bc7cda6758b1694667f1b5b0a97da5b75bb0c512fe2a695d2ee44768d8e3ceb275d07f222453d7eb79ad69dd98426156df021f4cbe
7
- data.tar.gz: ab28c8a9ab9cc625c30a573c17c2e4043d9498aef2f9cf90e01bcba2ed9d68d91c0d1708d59f2df27905cf6644617e1e99a6bd41d626d1ff7902f32209db7662
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/dummy/Rakefile", __FILE__)
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
@@ -1,4 +1,5 @@
1
- require "stagehand/engine"
1
+ require 'rails/all'
2
+ require 'stagehand/engine'
2
3
 
3
4
  module Stagehand
4
5
  end
@@ -1,6 +1,10 @@
1
+ require 'thread'
2
+
1
3
  ActiveRecord::Base.class_eval do
2
4
  # SYNC CALLBACKS
3
- define_model_callbacks :sync, :sync_as_subject, :sync_as_affected
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 ||= Stagehand::Schema.has_stagehand?(table_name)
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
- state = model.connection.readonly?
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!(true)
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] != Database.production_database_name
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 production database"
58
+ Rails.logger.warn "Writing directly to #{@config[:database]} database using readonly connection"
63
59
  else
64
- raise(UnsyncedProductionWrite, "Attempted to write directly to production database")
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
- skip_action_callback :use_production_database, options
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
- skip_action_callback :use_staging_database, options
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
@@ -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
- different = current_connection_name != connection_name.to_sym
54
-
55
- if different
56
- @@connection_name_stack.push(connection_name.to_sym)
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 connect_to(connection_name)
88
- ActiveRecord::Base.establish_connection(connection_name)
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
- @@connection_name_stack.last
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 StagingProbe < ActiveRecord::Base
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
- establish_connection(Configuration.staging_connection_name)
132
+ super(Configuration.staging_connection_name)
110
133
  end
111
134
 
112
135
  init_connection
113
136
  end
114
137
 
115
- class ProductionProbe < ActiveRecord::Base
138
+ class ProductionProbe < Probe
116
139
  self.abstract_class = true
117
140
 
118
141
  def self.init_connection
119
- establish_connection(Configuration.production_connection_name)
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
@@ -1,4 +1,4 @@
1
- require "stagehand/configuration"
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 intialized 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/staging"
20
- require "stagehand/production"
21
- require "stagehand/schema"
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
@@ -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 type of entry chosen prioritizes creates over updates, and deletes over creates.
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[:delete] + compact_entries[:insert] + compact_entries[:update]
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.infer_class(table_name)
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
- related = self.class.related_entries(@subject, @relation_filter)
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.infer_class(table_name, record_id = nil)
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
- root_class = classes.first || table_name.classify.constantize # Try loading the class if it isn't loaded yet
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 ||= self.class.infer_class(table_name, record_id)
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.instance_variable_set(:@new_record, false)
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
- # DUMMY CLASSES
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(:id => :desc).limit(BATCH_SIZE).to_a
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
- while entries = autosyncable_entries("id <= #{current}").limit(BATCH_SIZE).order(:id => :desc).to_a.presence do
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
@@ -1,3 +1,3 @@
1
1
  module Stagehand
2
- VERSION = "0.15.1"
2
+ VERSION = "1.0.3"
3
3
  end
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.15.1
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: 2017-11-23 00:00:00.000000000 Z
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.0'
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.0'
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.12
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