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