Class: Bible270::MigrationReconciler

Inherits:
Object
  • Object
show all
Defined in:
lib/bible270/migration_reconciler.rb

Overview

Repairs schema_migrations after a database is copied without matching migration bookkeeping. A migration is safe to mark only when every table, column and current index it contributes is already present.

Defined Under Namespace

Classes: Status

Constant Summary collapse

CHECKS =
{
  'CreateBible270Readers' => :check_readers,
  'CreateBible270Checkoffs' => :check_checkoffs,
  'CreateBible270Comments' => :check_comments,
  'CreateBible270SignInTokens' => :check_sign_in_tokens,
  'AddNamesToBible270Readers' => :check_names,
  'AddApprovalToBible270Comments' => :check_approval,
  'CreateBible270Settings' => :check_settings,
  'AddBibleVersionToBible270Readers' => :check_bible_version,
  'AddPartToBible270Checkoffs' => :check_checkoff_parts,
  'AddRememberTokenToBible270Readers' => :check_remember_token,
  'AddMentionNoticesToBible270Readers' => :check_mention_notices,
  'AddParentToBible270Comments' => :check_comment_parent,
  'CreateBible270Likes' => :check_likes,
  'AddBlueLetterBibleToBible270Readers' => :check_blue_letter_bible,
  'AddPassageSourceToBible270Readers' => :check_passage_source,
  'AddDailyRemindersToBible270Readers' => :check_daily_reminders,
  'AddReflectionsSeenAtToBible270Readers' => :check_reflections_seen_at,
  'AddAllCommentNoticesToBible270Readers' => :check_all_comment_notices,
  'ChangePassageSourceDefaultToBibleCom' => :check_passage_source_default,
  'CreateActiveStorageTables' => :check_active_storage
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection:, migration_context:) ⇒ MigrationReconciler

Returns a new instance of MigrationReconciler.



40
41
42
43
# File 'lib/bible270/migration_reconciler.rb', line 40

def initialize(connection:, migration_context:)
  @connection = connection
  @migration_context = migration_context
end

Class Method Details

.for_current_databaseObject



35
36
37
38
# File 'lib/bible270/migration_reconciler.rb', line 35

def self.for_current_database
  context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths)
  new(connection: ActiveRecord::Base.connection, migration_context: context)
end

Instance Method Details

#check_active_storageObject

The Bible270 installer can install Active Storage for reader avatars. A copied database can therefore have the same timestamp mismatch for Rails' generated Active Storage migration as it has for the engine migrations.



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/bible270/migration_reconciler.rb', line 210

def check_active_storage
  table_missing(
    :active_storage_blobs,
    columns: %i[id key filename content_type metadata service_name byte_size checksum created_at],
    indexes: [{ columns: %i[key], unique: true }]
  ) + table_missing(
    :active_storage_attachments,
    columns: %i[id name record_type record_id blob_id created_at],
    indexes: [
      { columns: %i[blob_id] },
      { columns: %i[record_type record_id name blob_id], unique: true }
    ],
    foreign_keys: [{ column: :blob_id, to_table: :active_storage_blobs }]
  ) + table_missing(
    :active_storage_variant_records,
    columns: %i[id blob_id variation_digest],
    indexes: [{ columns: %i[blob_id variation_digest], unique: true }],
    foreign_keys: [{ column: :blob_id, to_table: :active_storage_blobs }]
  )
end

#check_all_comment_noticesObject



195
196
197
# File 'lib/bible270/migration_reconciler.rb', line 195

def check_all_comment_notices
  table_missing(:bible270_readers, columns: %i[notify_on_all_comments])
end

#check_approvalObject



110
111
112
113
114
115
116
# File 'lib/bible270/migration_reconciler.rb', line 110

def check_approval
  table_missing(
    :bible270_comments,
    columns: %i[approved moderated_at],
    indexes: [{ columns: %i[approved day] }]
  )
end

#check_bible_versionObject



126
127
128
# File 'lib/bible270/migration_reconciler.rb', line 126

def check_bible_version
  table_missing(:bible270_readers, columns: %i[bible_version])
end

#check_blue_letter_bibleObject



174
175
176
177
178
# File 'lib/bible270/migration_reconciler.rb', line 174

def check_blue_letter_bible
  return [] if connection.column_exists?(:bible270_readers, :passage_source)

  table_missing(:bible270_readers, columns: %i[blue_letter_bible])
end

#check_checkoff_partsObject



130
131
132
133
134
135
136
# File 'lib/bible270/migration_reconciler.rb', line 130

def check_checkoff_parts
  table_missing(
    :bible270_checkoffs,
    columns: %i[part],
    indexes: [{ columns: %i[reader_id day track part], unique: true }]
  )
end

#check_checkoffsObject



73
74
75
76
77
78
79
80
# File 'lib/bible270/migration_reconciler.rb', line 73

def check_checkoffs
  table_missing(
    :bible270_checkoffs,
    columns: %i[id reader_id day track created_at updated_at],
    indexes: [{ columns: %i[reader_id] }, { columns: %i[day] }],
    foreign_keys: [{ column: :reader_id, to_table: :bible270_readers }]
  )
end

#check_comment_parentObject



150
151
152
153
154
155
156
157
# File 'lib/bible270/migration_reconciler.rb', line 150

def check_comment_parent
  table_missing(
    :bible270_comments,
    columns: %i[parent_id],
    indexes: [{ columns: %i[parent_id] }],
    foreign_keys: [{ column: :parent_id, to_table: :bible270_comments }]
  )
end

#check_commentsObject



82
83
84
85
86
87
88
89
# File 'lib/bible270/migration_reconciler.rb', line 82

def check_comments
  table_missing(
    :bible270_comments,
    columns: %i[id reader_id day track body created_at updated_at],
    indexes: [{ columns: %i[reader_id] }, { columns: %i[day created_at] }],
    foreign_keys: [{ column: :reader_id, to_table: :bible270_readers }]
  )
end

#check_daily_remindersObject



184
185
186
187
188
189
# File 'lib/bible270/migration_reconciler.rb', line 184

def check_daily_reminders
  table_missing(
    :bible270_readers,
    columns: %i[daily_reminders daily_reminder_time last_daily_reminder_sent_on]
  )
end

#check_likesObject



159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/bible270/migration_reconciler.rb', line 159

def check_likes
  table_missing(
    :bible270_likes,
    columns: %i[id reader_id comment_id created_at updated_at],
    indexes: [
      { columns: %i[reader_id comment_id], unique: true },
      { columns: %i[comment_id] }
    ],
    foreign_keys: [
      { column: :reader_id, to_table: :bible270_readers },
      { column: :comment_id, to_table: :bible270_comments }
    ]
  )
end

#check_mention_noticesObject



146
147
148
# File 'lib/bible270/migration_reconciler.rb', line 146

def check_mention_notices
  table_missing(:bible270_readers, columns: %i[notify_on_mention])
end

#check_namesObject



102
103
104
105
106
107
108
# File 'lib/bible270/migration_reconciler.rb', line 102

def check_names
  table_missing(
    :bible270_readers,
    columns: %i[first_name last_name],
    indexes: [{ columns: %i[last_name first_name] }]
  ) + table_missing(:bible270_sign_in_tokens, columns: %i[first_name last_name])
end

#check_passage_sourceObject



180
181
182
# File 'lib/bible270/migration_reconciler.rb', line 180

def check_passage_source
  table_missing(:bible270_readers, columns: %i[passage_source])
end

#check_passage_source_defaultObject



199
200
201
202
203
204
205
# File 'lib/bible270/migration_reconciler.rb', line 199

def check_passage_source_default
  missing = table_missing(:bible270_readers, columns: %i[passage_source])
  return missing if missing.any?

  column = connection.columns(:bible270_readers).find { |candidate| candidate.name == 'passage_source' }
  column&.default == 'bible_com' ? [] : ['column bible270_readers.passage_source default bible_com']
end

#check_readersObject



62
63
64
65
66
67
68
69
70
71
# File 'lib/bible270/migration_reconciler.rb', line 62

def check_readers
  table_missing(
    :bible270_readers,
    columns: %i[id display_name email avatar_url provider uid owner_type owner_id started_on created_at updated_at],
    indexes: [
      { columns: %i[owner_type owner_id] },
      { columns: %i[provider uid], unique: true }
    ]
  )
end

#check_reflections_seen_atObject



191
192
193
# File 'lib/bible270/migration_reconciler.rb', line 191

def check_reflections_seen_at
  table_missing(:bible270_readers, columns: %i[reflections_seen_at])
end

#check_remember_tokenObject



138
139
140
141
142
143
144
# File 'lib/bible270/migration_reconciler.rb', line 138

def check_remember_token
  table_missing(
    :bible270_readers,
    columns: %i[remember_token],
    indexes: [{ columns: %i[remember_token] }]
  )
end

#check_settingsObject



118
119
120
121
122
123
124
# File 'lib/bible270/migration_reconciler.rb', line 118

def check_settings
  table_missing(
    :bible270_settings,
    columns: %i[id key value created_at updated_at],
    indexes: [{ columns: %i[key], unique: true }]
  )
end

#check_sign_in_tokensObject



91
92
93
94
95
96
97
98
99
100
# File 'lib/bible270/migration_reconciler.rb', line 91

def 
  table_missing(
    :bible270_sign_in_tokens,
    columns: %i[id email token_digest display_name expires_at consumed_at created_at updated_at],
    indexes: [
      { columns: %i[token_digest], unique: true },
      { columns: %i[email created_at] }
    ]
  )
end

#mark_applied!(status) ⇒ Object

Raises:

  • (ArgumentError)


56
57
58
59
60
# File 'lib/bible270/migration_reconciler.rb', line 56

def mark_applied!(status)
  raise ArgumentError, "#{status.migration.name} is not reflected in the schema" unless status.complete?

  migration_context.schema_migration.create_version(status.migration.version.to_s)
end

#statusesObject



45
46
47
48
49
50
51
52
53
54
# File 'lib/bible270/migration_reconciler.rb', line 45

def statuses
  applied = migration_context.get_all_versions.map(&:to_i)
  migration_context.migrations.filter_map do |migration|
    check = CHECKS[migration.name]
    next unless check
    next if applied.include?(migration.version.to_i)

    Status.new(migration: migration, missing: public_send(check))
  end
end