bible270 0.17.2 → 0.18.1

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
  SHA256:
3
- metadata.gz: 9e3c27f2336f3e2a809205242bbc42c46d241e49b9ea2b1bf49ca288523d7c88
4
- data.tar.gz: 8e8c25b59eafde481a17546ac14b563527dd591ca64d9cf7541d95a01dd1f36c
3
+ metadata.gz: 267adcf001d1af00e789992fdec3bbd27bd6094408863e6b66e4a6b33eb7fd25
4
+ data.tar.gz: 1b99bedad8348dda8bc1af3f7e57def64f4097884074079898bf2d8f449609a1
5
5
  SHA512:
6
- metadata.gz: 06af092550c30ded960e3611301d941e10fdea25e79fcfcab8aa97c4d68fee7b3780961f22bc4f81be4ba3f065ebe57642386ba318c69f80e2f1bd1e07ce0dec
7
- data.tar.gz: ed69c75af0e88e8dfeb347b756c6c10692f46b6e90196e96851aecc026a664752ce6f2fbb3de3b324734eff316555c229d19aa15270d26061b370b4db643f3ae
6
+ metadata.gz: 73a9a0d0f506ed7a64598c2c0b11104a0f6d0877e82ce6c2ee040211342bce0c95e289c41dba1f555d352fecf259cf5f586bf250e2f26d970145cc66b5d1e87b
7
+ data.tar.gz: e393b0f5843224d5390d8f5e90a0022b55710c47d89791eea0dfee462815a75625c55682e2cbfa526233cd0b3fe24364e78d7dbffdd98d0b2c00655a9b60ae84
data/CHANGELOG.md CHANGED
@@ -6,7 +6,66 @@ All notable changes to bible270. Format follows [Keep a Changelog](https://keepa
6
6
 
7
7
  See the changes since the last release:
8
8
 
9
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.16.0...main)**
9
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.18.0...main)**
10
+
11
+ ## [0.18.0] - 2026-08-10
12
+
13
+ ### Added
14
+
15
+ - `HEB/GRK` reading option with WLC links for the Old Testament, Psalms, and Proverbs, and mGNT links for the New Testament
16
+ - per-reader Bible Gateway or Blue Letter Bible source selection in Profile, with the same control available to admins
17
+ - `bible270:migrations:reconcile` task to safely align copied Bible270 and Active Storage schemas with local migration versions
18
+
19
+ ### Changed
20
+
21
+ - original-language reading labels now display `Hebrew` or `Greek` for the relevant passage
22
+ - passage-source preferences use an extensible string value, defaulting to Bible Gateway and preserving earlier Blue Letter Bible selections during migration
23
+
24
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.17.2...v0.18.0)**
25
+
26
+ ## [0.17.2] - 2026-08-08
27
+
28
+ ### Added
29
+
30
+ - reader Bible translation preferences editable by admin users
31
+ - admin broadcast email tools
32
+
33
+ ### Changed
34
+
35
+ - extract first and last names from OmniAuth display names
36
+ - expand CI coverage reporting and add Ruby 4.0 coverage
37
+
38
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.17.1...v0.17.2)**
39
+
40
+ ## [0.17.1] - 2026-08-06
41
+
42
+ ### Added
43
+
44
+ - likes for reflections
45
+
46
+ ### Changed
47
+
48
+ - display timestamps using the local time zone
49
+ - rename reflections to 'posts' in UI headers, because we like alliteration
50
+
51
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.17.0...v0.17.1)**
52
+
53
+ ## [0.17.0] - 2026-08-05
54
+
55
+ ### Added
56
+
57
+ - a reflections page for browsing recent comments
58
+ - the ability for readers to edit their reflections
59
+
60
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.16.1...v0.17.0)**
61
+
62
+ ## [0.16.1] - 2026-08-05
63
+
64
+ ### Changed
65
+
66
+ - refactor mailer URL helpers to use the engine's routes
67
+
68
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.16.0...v0.16.1)**
10
69
 
11
70
  ## [0.16.0] - 2026-08-05
12
71
 
@@ -19,8 +78,6 @@ See the changes since the last release:
19
78
 
20
79
  ## [0.15.3] - 2026-08-04
21
80
 
22
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.2...v0.15.3)**
23
-
24
81
  ### Added
25
82
 
26
83
  - `Bible270::Checkoff.reset_column_information` to migrations to prevent `UnknownAttributeError` during backfills.
@@ -30,9 +87,9 @@ See the changes since the last release:
30
87
 
31
88
  - 'Today' now derived from system time, not UTC
32
89
 
33
- ## [0.15.2] - 2026-08-04
90
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.2...v0.15.3)**
34
91
 
35
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.1...v0.15.2)**
92
+ ## [0.15.2] - 2026-08-04
36
93
 
37
94
  ### Added
38
95
 
@@ -42,9 +99,9 @@ See the changes since the last release:
42
99
 
43
100
  - consistent reader sort order by first name in 'People' and 'Admin'
44
101
 
45
- ## [0.15.1] - 2026-08-03
102
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.1...v0.15.2)**
46
103
 
47
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.0...v0.15.1)**
104
+ ## [0.15.1] - 2026-08-03
48
105
 
49
106
  ### Added
50
107
 
@@ -59,9 +116,9 @@ See the changes since the last release:
59
116
 
60
117
  - old zip file of tests
61
118
 
62
- ## [0.15.0] - 2026-08-02
119
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.0...v0.15.1)**
63
120
 
64
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.14.1...v0.15.0)**
121
+ ## [0.15.0] - 2026-08-02
65
122
 
66
123
  ### Added
67
124
 
@@ -73,9 +130,9 @@ See the changes since the last release:
73
130
 
74
131
  - mobile session requiring sign-in every time: use persistent sessions using 'remember-me' cookie
75
132
 
76
- ## [0.14.1] - 2026-07-30
133
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.14.1...v0.15.0)**
77
134
 
78
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.14.0...v0.14.1)**
135
+ ## [0.14.1] - 2026-07-30
79
136
 
80
137
  ### Changed
81
138
 
@@ -87,9 +144,9 @@ See the changes since the last release:
87
144
  - display 'Start' not 'Continue' if reader is on day 1
88
145
  - adjust badge vertical alignment in styles
89
146
 
90
- ## [0.14.0] - 2026-07-30
147
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.14.0...v0.14.1)**
91
148
 
92
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.13.1...v0.14.0)**
149
+ ## [0.14.0] - 2026-07-30
93
150
 
94
151
  ### Added
95
152
 
@@ -107,16 +164,10 @@ See the changes since the last release:
107
164
  wrote an initializer that crashed the host app on boot. A test now renders every generator template
108
165
  and compiles the result, across three mount points.
109
166
 
110
- ## [0.13.1] — 2026-07-29
111
-
112
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.13.0...v0.13.1)**
113
-
114
- Nothing consequential.
167
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.13.0...v0.14.0)**
115
168
 
116
169
  ## [0.13.0] — 2026-07-29
117
170
 
118
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.12.0...v0.13.0)**
119
-
120
171
  ### Added
121
172
 
122
173
  - favicon support
@@ -127,9 +178,9 @@ Nothing consequential.
127
178
 
128
179
  - Correct verb for reading prompt: 'Start' and 'Continue
129
180
 
130
- ## [0.12.0] — 2026-07-29
181
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.12.0...v0.13.0)**
131
182
 
132
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.11.0...v0.12.0)**
183
+ ## [0.12.0] — 2026-07-29
133
184
 
134
185
  ### Added
135
186
 
@@ -141,9 +192,9 @@ Nothing consequential.
141
192
  - Top menu shows 'Progress' in place of user display name
142
193
  - Dependabot gem updates to minitest and rubocop
143
194
 
144
- ## [0.11.0] — 2026-07-28
195
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.11.0...v0.12.0)**
145
196
 
146
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.10.0...v0.11.0)**
197
+ ## [0.11.0] — 2026-07-28
147
198
 
148
199
  ### Added
149
200
 
@@ -195,9 +246,9 @@ Nothing consequential.
195
246
  - Migrations: `20260101000007` (settings). Active Storage tables are the host app's
196
247
  (`bin/rails active_storage:install`).
197
248
 
198
- ## [0.10.0] — 2026-07-26
249
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.10.0...v0.11.0)**
199
250
 
200
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.9.0...v0.10.0)**
251
+ ## [0.10.0] — 2026-07-26
201
252
 
202
253
  ### Reading plan
203
254
 
@@ -270,9 +321,9 @@ Nothing consequential.
270
321
 
271
322
  - Table names changed from `bible_reading_plan_*` in the 0.9.0 line; this is a fresh install.
272
323
 
273
- ## [0.9.0] — 2026-07-25
324
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.9.0...v0.10.0)**
274
325
 
275
- **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.6.2...v0.9.0)**
326
+ ## [0.9.0] — 2026-07-25
276
327
 
277
328
  ### Changed
278
329
 
@@ -290,6 +341,8 @@ Nothing consequential.
290
341
  * `Plan.nt_groups`, `format_pp_segment`, `pp_segment_length` (superseded by the shared `format_segment` and `segment_length`), and `totals[:nt_rest_days]`.
291
342
  * `nt_rest_days` remains but now always returns an empty array.
292
343
 
344
+ **[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.6.2...v0.9.0)**
345
+
293
346
  ## [0.6.2] — 2026-07-25
294
347
 
295
348
  This is the initial public release.
@@ -352,6 +405,11 @@ This is the initial public release.
352
405
 
353
406
  * Email sign-in requires working Action Mailer delivery in the host application. Set `config.mailer_from`; delivery is inline by default, or set `email_sign_in_deliver_later` when a queue backend is available.
354
407
 
408
+ [0.18.0]: https://github.com/avonderluft/bible270/releases/tag/v0.18.0
409
+ [0.17.2]: https://github.com/avonderluft/bible270/releases/tag/v0.17.2
410
+ [0.17.1]: https://github.com/avonderluft/bible270/releases/tag/v0.17.1
411
+ [0.17.0]: https://github.com/avonderluft/bible270/releases/tag/v0.17.0
412
+ [0.16.1]: https://github.com/avonderluft/bible270/releases/tag/v0.16.1
355
413
  [0.16.0]: https://github.com/avonderluft/bible270/releases/tag/v0.16.0
356
414
  [0.15.3]: https://github.com/avonderluft/bible270/releases/tag/v0.15.3
357
415
  [0.15.2]: https://github.com/avonderluft/bible270/releases/tag/v0.15.2
@@ -359,7 +417,6 @@ This is the initial public release.
359
417
  [0.15.0]: https://github.com/avonderluft/bible270/releases/tag/v0.15.0
360
418
  [0.14.1]: https://github.com/avonderluft/bible270/releases/tag/v0.14.1
361
419
  [0.14.0]: https://github.com/avonderluft/bible270/releases/tag/v0.14.0
362
- [0.13.1]: https://github.com/avonderluft/bible270/releases/tag/v0.13.1
363
420
  [0.13.0]: https://github.com/avonderluft/bible270/releases/tag/v0.13.0
364
421
  [0.12.0]: https://github.com/avonderluft/bible270/releases/tag/v0.12.0
365
422
  [0.11.0]: https://github.com/avonderluft/bible270/releases/tag/v0.11.0
data/README.md CHANGED
@@ -117,6 +117,29 @@ bin/rails bible270:install:migrations # skips ones you already have
117
117
  bin/rails db:migrate
118
118
  ```
119
119
 
120
+ ### Reconciling a copied database
121
+
122
+ A copied database can contain the Bible270 tables while its `schema_migrations` versions do not match
123
+ the timestamped migration copies in the local app. In that case, preview the reconciliation:
124
+
125
+ ```bash
126
+ bin/rails bible270:migrations:reconcile
127
+ ```
128
+
129
+ The task recognizes Bible270 migrations—and the related generated Active Storage migration used for
130
+ reader avatars—by name, then verifies their tables, columns, current indexes, and foreign keys. It
131
+ does not treat an existing table alone as sufficient. After reviewing the list and backing up the
132
+ database, record only the migrations already reflected in the schema:
133
+
134
+ ```bash
135
+ APPLY=1 bin/rails bible270:migrations:reconcile
136
+ bin/rails db:migrate
137
+ ```
138
+
139
+ Preview mode never writes to the database. Apply mode only inserts verified local versions into
140
+ `schema_migrations`; it does not create, remove, or alter application tables. Migrations whose changes
141
+ are missing remain pending for the final `db:migrate` command.
142
+
120
143
  ## Bible270 Mount point in your Rails app
121
144
 
122
145
  The path lives in exactly **one** place, `config.mount_at`:
@@ -361,7 +384,10 @@ config.favicon = false # none; the host's favicon applies
361
384
  It's emitted by the engine's layout, so if you point `config.layout` at your own layout, add
362
385
  `<%= b270_favicon_tag %>` to its `<head>`.
363
386
 
364
- References link to Bible Gateway by default point `passage_url_builder` at your another reader if you wish, or your own reader if you host one.
387
+ References link to Bible Gateway by default. Readers choose Bible Gateway or Blue Letter Bible from
388
+ their profile, and an admin can set the same source for any reader. The `HEB/GRK` option always uses
389
+ Blue Letter Bible: WLC for Old Testament, Psalms, and Proverbs readings, and mGNT for New Testament
390
+ readings. Set `passage_url_builder` to use another default reader, or a reader you host yourself.
365
391
 
366
392
  ## With ComfortableMediaSurfer
367
393
 
@@ -371,7 +397,7 @@ The CMS serves your content; this is a separate mounted app. Either **just link
371
397
 
372
398
  | Table | Holds |
373
399
  |-------|-------|
374
- | `bible270_readers` | identity: display name, avatar, provider/uid or polymorphic `owner`, `started_on` |
400
+ | `bible270_readers` | identity, avatar, provider/uid or polymorphic `owner`, start date, translation and passage-reader preferences |
375
401
  | `bible270_checkoffs` | one row per reader per day per track (`ot`/`nt`/`pp`), unique-indexed |
376
402
  | `bible270_comments` | a reflection on a day, optionally scoped to a track, public to all |
377
403
  | `bible270_sign_in_tokens` | magic-link tokens: email, **digest only**, expiry, consumed-at |
@@ -105,16 +105,22 @@ module Bible270
105
105
  return redirect_to(admin_path, alert: 'No such reader.') if reader.nil?
106
106
 
107
107
  code = params[:bible_version].to_s
108
+ source = params[:passage_source].presence || Reader::DEFAULT_PASSAGE_SOURCE
109
+ unless Reader::PASSAGE_SOURCES.include?(source)
110
+ return redirect_to admin_reader_path(reader), alert: 'That is not a reading-link source on the list.'
111
+ end
112
+
108
113
  # Blank means "no preference": the reader follows the site default, which is
109
114
  # not the same as choosing whichever translation the site happens to use now.
110
115
  if code.empty?
111
- reader.update(bible_version: nil)
116
+ reader.update(bible_version: nil, passage_source: source)
112
117
  return redirect_to admin_reader_path(reader),
113
118
  notice: "#{reader.display_name} now follows the site default " \
114
119
  "(#{Bible270.config.bible_version})."
115
120
  end
116
121
 
117
122
  if reader.update_bible_version(code)
123
+ reader.update_column(:passage_source, source)
118
124
  redirect_to admin_reader_path(reader),
119
125
  notice: "#{reader.display_name} now reads the #{reader.bible_version_label}."
120
126
  else
@@ -21,6 +21,8 @@ module Bible270
21
21
  if params[:bible_version].present? && !current_reader.update_bible_version(params[:bible_version])
22
22
  problems << 'a translation from the list'
23
23
  end
24
+ source = params[:passage_source].presence || Reader::DEFAULT_PASSAGE_SOURCE
25
+ problems << 'a reading-link source from the list' unless current_reader.update_passage_source(source)
24
26
  if params[:avatar].present? && !current_reader.attach_avatar(params[:avatar])
25
27
  problems << (current_reader.errors[:avatar].first || 'a valid image')
26
28
  end
@@ -112,13 +112,24 @@ module Bible270
112
112
  def b270_passage_url(reference, version = nil)
113
113
  return '#' if reference.blank?
114
114
 
115
- Bible270.config.passage_url_builder.call(reference, version || b270_bible_version)
115
+ selected_version = version || b270_bible_version
116
+ if selected_version != Translations::ORIGINAL_LANGUAGES && current_reader&.blue_letter_bible?
117
+ return Translations.passage_url(reference, selected_version, blue_letter: true)
118
+ end
119
+
120
+ Bible270.config.passage_url_builder.call(reference, selected_version)
116
121
  end
117
122
 
118
123
  # Small grey "(NKJV)" after a reading, so it's clear which translation the
119
- # link opens in.
120
- def b270_version_tag(version = nil)
121
- content_tag :span, "(#{version || b270_bible_version})", class: 'b270-version'
124
+ # link opens in. The original-language preference names the actual language
125
+ # used for each track rather than repeating the combined preference code.
126
+ def b270_version_tag(version = nil, track: nil)
127
+ label = version || b270_bible_version
128
+ if label == Translations::ORIGINAL_LANGUAGES && track
129
+ label = track.to_s == 'nt' ? 'Greek' : 'Hebrew'
130
+ end
131
+
132
+ content_tag :span, "(#{label})", class: 'b270-version'
122
133
  end
123
134
 
124
135
  def b270_avatar_src(reader)
@@ -127,7 +138,7 @@ module Bible270
127
138
  reader.avatar_url.presence
128
139
  end
129
140
 
130
- def b270_avatar(reader, size: 34)
141
+ def b270_avatar(reader, size: 72)
131
142
  src = b270_avatar_src(reader)
132
143
  if src
133
144
  image_tag src, class: 'b270-avatar', width: size, height: size, alt: reader.display_name
@@ -25,8 +25,12 @@ module Bible270
25
25
  # transaction rolls back.
26
26
  after_create_commit :notify_of_registration
27
27
 
28
+ PASSAGE_SOURCES = %w[bible_gateway blue_letter_bible].freeze
29
+ DEFAULT_PASSAGE_SOURCE = 'bible_gateway'
30
+
28
31
  validates :display_name, presence: true
29
32
  validates :uid, uniqueness: { scope: :provider }, allow_nil: true
33
+ validates :passage_source, inclusion: { in: PASSAGE_SOURCES }
30
34
 
31
35
  # Build/refresh a reader from an OmniAuth auth hash. Tolerant of the various
32
36
  # shapes strategies return (OmniAuth::AuthHash, plain Hash, missing info).
@@ -164,6 +168,16 @@ module Bible270
164
168
  update(bible_version: Translations.normalize(code))
165
169
  end
166
170
 
171
+ def update_passage_source(source)
172
+ source = source.to_s
173
+ return false unless PASSAGE_SOURCES.include?(source)
174
+
175
+ update(passage_source: source)
176
+ end
177
+
178
+ def bible_gateway? = passage_source == 'bible_gateway'
179
+ def blue_letter_bible? = passage_source == 'blue_letter_bible'
180
+
167
181
  # Readers are listed by first name, matching how they are shown — the display
168
182
  # name is "First Last", so sorting on it needs only case folding. Surname
169
183
  # order was inconsistent with the community page and read oddly next to names
@@ -5,7 +5,7 @@
5
5
  <%= link_to 'Moderate reflections →', admin_comments_path %></p>
6
6
 
7
7
  <div class="b270-panel">
8
- <h2 class="b270-subhead">This run of the plan is <%= @enrollment_closed ? 'closed' : 'open' %> to new readers.</h2>
8
+ <h2 class="b270-subhead">This run of the plan is <%= @enrollment_closed ? 'closed' : 'open' %> to new readers.</h2>
9
9
  <% if @enrollment_closed %>
10
10
  <% if @enrollment_closed_at %>
11
11
  <p class="b270-startline">Enrollment closed
@@ -26,7 +26,7 @@
26
26
  <div class="b270-list">
27
27
  <% @readers.each do |reader| %>
28
28
  <div class="b270-li">
29
- <%= b270_avatar(reader, size: 34) %>
29
+ <%= b270_avatar(reader, size: 72) %>
30
30
  <div>
31
31
  <div class="name"><%= link_to reader.display_name, admin_reader_path(reader), style: 'text-decoration:none' %>
32
32
  <% if Bible270.config.admin?(reader) %> <span class="sub"><strong>[admin]</strong></span> <% end %>
@@ -16,7 +16,7 @@
16
16
  <%= form_with url: admin_reader_profile_path(@reader), method: :patch, class: "b270-emailform",
17
17
  html: { multipart: true } do %>
18
18
  <div class="b270-avatarrow">
19
- <%= b270_avatar(@reader, size: 64) %>
19
+ <%= b270_avatar(@reader, size: 72) %>
20
20
  <% if Bible270::Reader.avatar_uploads? %>
21
21
  <label class="b270-field" style="flex:1">
22
22
  <span>Picture</span>
@@ -109,8 +109,9 @@
109
109
  Bible270::Translations.options,
110
110
  @reader.bible_version.to_s),
111
111
  class: "b270-select", style: "width:auto" %>
112
- <%= submit_tag "Save", class: "b270-btn" %>
113
112
  </div>
113
+ <%= render "bible270/shared/passage_source", reader: @reader %>
114
+ <%= submit_tag "Save", class: "b270-btn" %>
114
115
  <% end %>
115
116
  </div>
116
117
 
@@ -7,7 +7,7 @@
7
7
  %>
8
8
  <% editing = @editing_comment_id.present? && @editing_comment_id == comment.id %>
9
9
  <div class="b270-comment <%= 'b270-reply' if reply %>" id="comment-<%= comment.id %>">
10
- <%= b270_avatar(comment.reader, size: reply ? 28 : 34) %>
10
+ <%= b270_avatar(comment.reader, size: reply ? 36 : 48) %>
11
11
  <div style="flex:1">
12
12
  <div class="b270-cmeta">
13
13
  <strong><%= link_to comment.reader.display_name, reader_path(comment.reader), style: "text-decoration:none" %></strong>
@@ -35,7 +35,7 @@
35
35
  <% end %>
36
36
  </ul>
37
37
  <div class="b270-parts-meta">
38
- <%= read_parts.size %> of <%= parts.size %> chapters <%= b270_version_tag %>
38
+ <%= read_parts.size %> of <%= parts.size %> chapters <%= b270_version_tag(track: track) %>
39
39
  </div>
40
40
  </div>
41
41
  <% else %>
@@ -50,7 +50,7 @@
50
50
  <% end %>
51
51
  <div class="b270-rbody">
52
52
  <div class="b270-cat"><%= t[:label] %></div>
53
- <div class="b270-ref"><%= link_to ref, b270_passage_url(ref), target: "_blank", rel: "noopener" %> <%= b270_version_tag %></div>
53
+ <div class="b270-ref"><%= link_to ref, b270_passage_url(ref), target: "_blank", rel: "noopener" %> <%= b270_version_tag(track: track) %></div>
54
54
  </div>
55
55
  <% end %>
56
56
  </div>
@@ -7,7 +7,7 @@
7
7
  <%= form_with url: profile_path, method: :patch, class: "b270-emailform",
8
8
  html: { multipart: true } do %>
9
9
  <div class="b270-avatarrow">
10
- <%= b270_avatar(current_reader, size: 64) %>
10
+ <%= b270_avatar(current_reader, size: 72) %>
11
11
  <% if Bible270::Reader.avatar_uploads? %>
12
12
  <label class="b270-field" style="flex:1">
13
13
  <span>Picture</span>
@@ -39,6 +39,7 @@
39
39
  class: "b270-select" %>
40
40
  <span class="b270-hint" style="margin:0">Reading links open in this translation.</span>
41
41
  </label>
42
+ <%= render "bible270/shared/passage_source", reader: current_reader %>
42
43
 
43
44
  <%= submit_tag "Save", class: "b270-btn" %>
44
45
  <% end %>
@@ -8,7 +8,7 @@
8
8
  <% @readers.each_with_index do |r, i| %>
9
9
  <div class="b270-li">
10
10
  <span class="rank"><%= i + 1 %></span>
11
- <%= b270_avatar(r, size: 40) %>
11
+ <%= b270_avatar(r, size: 72) %>
12
12
  <div>
13
13
  <div class="name"><%= link_to r.display_name, reader_path(r), style: "text-decoration:none" %></div>
14
14
  <div class="sub"><%= @days_completed[r.id] %> of <%= Bible270::Plan::DAYS %> days · joined <%= Bible270.local_time(r.created_at).strftime("%b %Y") %></div>
@@ -1,7 +1,7 @@
1
1
  <% content_for :title, "#{@reader.display_name} — #{Bible270.config.app_name}" %>
2
2
  <% content_for :hide_day_index, true %><%# this page has its own grid %>
3
3
  <div style="display:flex;align-items:center;gap:16px;margin-bottom:8px">
4
- <%= b270_avatar(@reader, size: 56) %>
4
+ <%= b270_avatar(@reader, size: 72) %>
5
5
  <div>
6
6
  <h1 style="margin:0"><%= @reader.display_name %></h1>
7
7
  <% if signed_in? && current_reader.id == @reader.id %>
@@ -26,4 +26,3 @@
26
26
  <% end %>
27
27
  </div>
28
28
  <p class="b270-gridnote">Dot indicates days with reflections.</p>
29
-
@@ -0,0 +1,13 @@
1
+ <% original_languages = reader.effective_bible_version == Bible270::Translations::ORIGINAL_LANGUAGES %>
2
+ <div class="b270-source-choices" role="radiogroup" aria-label="Reading link source">
3
+ <label class="b270-source-choice <%= 'disabled' if original_languages %>">
4
+ <%= radio_button_tag :passage_source, "bible_gateway",
5
+ !original_languages && reader.bible_gateway?, disabled: original_languages %>
6
+ <span>Bible Gateway</span>
7
+ </label>
8
+ <label class="b270-source-choice <%= 'disabled' if original_languages %>">
9
+ <%= radio_button_tag :passage_source, "blue_letter_bible",
10
+ original_languages || reader.blue_letter_bible?, disabled: original_languages %>
11
+ <span>Blue Letter Bible</span>
12
+ </label>
13
+ </div>
@@ -134,6 +134,15 @@
134
134
  .b270-parts-meta{margin-top:10px;font-size:12.5px;color:var(--faint)}
135
135
  .b270-version{font-size:11.5px;color:var(--faint);font-weight:400;letter-spacing:.02em;white-space:nowrap}
136
136
  .b270-select{font:inherit;font-size:14px;padding:10px 30px 10px 12px;border:1px solid var(--line);border-radius:9px;background:#fff;color:var(--ink);width:100%;max-width:100%;text-overflow:ellipsis}
137
+ .b270-source-choices{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin:8px 0 2px;color:var(--muted);font-size:12.5px}
138
+ .b270-source-choice{display:inline-flex;align-items:center;gap:7px;cursor:pointer;white-space:nowrap}
139
+ .b270-source-choice input{appearance:none;width:17px;height:17px;margin:0;border:1px solid var(--line);border-radius:3px;background:#fff;display:grid;place-content:center;cursor:pointer}
140
+ .b270-source-choice input::before{content:"";width:9px;height:9px;transform:scale(0);transition:transform .1s;clip-path:polygon(14% 44%,0 59%,38% 100%,100% 18%,82% 0,37% 65%);background:var(--paper)}
141
+ .b270-source-choice input:checked{background:var(--gold);border-color:var(--gold)}
142
+ .b270-source-choice input:checked::before{transform:scale(1)}
143
+ .b270-source-choice input:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
144
+ .b270-source-choice.disabled{cursor:default;opacity:.55}
145
+ .b270-source-choice input:disabled{cursor:default}
137
146
  .b270-ref a{text-decoration:none;border-bottom:1px solid transparent}
138
147
  .b270-ref a:hover{border-bottom-color:var(--gold)}
139
148
  .b270-reading.done .b270-ref{color:var(--muted)}
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddBlueLetterBibleToBible270Readers < ActiveRecord::Migration[7.0]
4
+ def change
5
+ add_column :bible270_readers, :blue_letter_bible, :boolean, default: false, null: false
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddPassageSourceToBible270Readers < ActiveRecord::Migration[7.0]
4
+ def up
5
+ add_column :bible270_readers, :passage_source, :string, default: 'bible_gateway', null: false
6
+
7
+ return unless column_exists?(:bible270_readers, :blue_letter_bible)
8
+
9
+ execute <<~SQL.squish
10
+ UPDATE bible270_readers
11
+ SET passage_source = 'blue_letter_bible'
12
+ WHERE blue_letter_bible = TRUE
13
+ SQL
14
+ remove_column :bible270_readers, :blue_letter_bible
15
+ end
16
+
17
+ def down
18
+ add_column :bible270_readers, :blue_letter_bible, :boolean, default: false, null: false
19
+ execute <<~SQL.squish
20
+ UPDATE bible270_readers
21
+ SET blue_letter_bible = TRUE
22
+ WHERE passage_source = 'blue_letter_bible'
23
+ SQL
24
+ remove_column :bible270_readers, :passage_source
25
+ end
26
+ end
@@ -347,12 +347,7 @@ module Bible270
347
347
  @after_sign_out_path = nil
348
348
  @bible_version = 'NKJV'
349
349
  @bible_versions = Translations::VERSIONS.keys
350
- @passage_url_builder = ->(reference, version) do
351
- search = URI.encode_www_form_component(reference)
352
- # Translations.gateway_code maps 'NASB95' to Bible Gateway's 'NASB1995'.
353
- version = URI.encode_www_form_component(Translations.gateway_code(version))
354
- "https://www.biblegateway.com/passage/?search=#{search}&version=#{version}"
355
- end
350
+ @passage_url_builder = ->(reference, version) { Translations.passage_url(reference, version) }
356
351
  @app_name = 'Daily Bread'
357
352
  @tagline = 'Journeying through Scripture in 9 months'
358
353
  @time_zone = nil
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bible270
4
+ # Repairs schema_migrations after a database is copied without matching migration
5
+ # bookkeeping. A migration is safe to mark only when every table, column and
6
+ # current index it contributes is already present.
7
+ class MigrationReconciler
8
+ Status = Struct.new(:migration, :missing, keyword_init: true) do
9
+ def complete? = missing.empty?
10
+ end
11
+
12
+ CHECKS = {
13
+ 'CreateBible270Readers' => :check_readers,
14
+ 'CreateBible270Checkoffs' => :check_checkoffs,
15
+ 'CreateBible270Comments' => :check_comments,
16
+ 'CreateBible270SignInTokens' => :check_sign_in_tokens,
17
+ 'AddNamesToBible270Readers' => :check_names,
18
+ 'AddApprovalToBible270Comments' => :check_approval,
19
+ 'CreateBible270Settings' => :check_settings,
20
+ 'AddBibleVersionToBible270Readers' => :check_bible_version,
21
+ 'AddPartToBible270Checkoffs' => :check_checkoff_parts,
22
+ 'AddRememberTokenToBible270Readers' => :check_remember_token,
23
+ 'AddMentionNoticesToBible270Readers' => :check_mention_notices,
24
+ 'AddParentToBible270Comments' => :check_comment_parent,
25
+ 'CreateBible270Likes' => :check_likes,
26
+ 'AddBlueLetterBibleToBible270Readers' => :check_blue_letter_bible,
27
+ 'AddPassageSourceToBible270Readers' => :check_passage_source,
28
+ 'CreateActiveStorageTables' => :check_active_storage
29
+ }.freeze
30
+
31
+ def self.for_current_database
32
+ context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths)
33
+ new(connection: ActiveRecord::Base.connection, migration_context: context)
34
+ end
35
+
36
+ def initialize(connection:, migration_context:)
37
+ @connection = connection
38
+ @migration_context = migration_context
39
+ end
40
+
41
+ def statuses
42
+ applied = migration_context.get_all_versions.map(&:to_i)
43
+ migration_context.migrations.filter_map do |migration|
44
+ check = CHECKS[migration.name]
45
+ next unless check
46
+ next if applied.include?(migration.version.to_i)
47
+
48
+ Status.new(migration: migration, missing: public_send(check))
49
+ end
50
+ end
51
+
52
+ def mark_applied!(status)
53
+ raise ArgumentError, "#{status.migration.name} is not reflected in the schema" unless status.complete?
54
+
55
+ migration_context.schema_migration.create_version(status.migration.version.to_s)
56
+ end
57
+
58
+ def check_readers
59
+ table_missing(
60
+ :bible270_readers,
61
+ columns: %i[id display_name email avatar_url provider uid owner_type owner_id started_on created_at updated_at],
62
+ indexes: [
63
+ { columns: %i[owner_type owner_id] },
64
+ { columns: %i[provider uid], unique: true }
65
+ ]
66
+ )
67
+ end
68
+
69
+ def check_checkoffs
70
+ table_missing(
71
+ :bible270_checkoffs,
72
+ columns: %i[id reader_id day track created_at updated_at],
73
+ indexes: [{ columns: %i[reader_id] }, { columns: %i[day] }],
74
+ foreign_keys: [{ column: :reader_id, to_table: :bible270_readers }]
75
+ )
76
+ end
77
+
78
+ def check_comments
79
+ table_missing(
80
+ :bible270_comments,
81
+ columns: %i[id reader_id day track body created_at updated_at],
82
+ indexes: [{ columns: %i[reader_id] }, { columns: %i[day created_at] }],
83
+ foreign_keys: [{ column: :reader_id, to_table: :bible270_readers }]
84
+ )
85
+ end
86
+
87
+ def check_sign_in_tokens
88
+ table_missing(
89
+ :bible270_sign_in_tokens,
90
+ columns: %i[id email token_digest display_name expires_at consumed_at created_at updated_at],
91
+ indexes: [
92
+ { columns: %i[token_digest], unique: true },
93
+ { columns: %i[email created_at] }
94
+ ]
95
+ )
96
+ end
97
+
98
+ def check_names
99
+ table_missing(
100
+ :bible270_readers,
101
+ columns: %i[first_name last_name],
102
+ indexes: [{ columns: %i[last_name first_name] }]
103
+ ) + table_missing(:bible270_sign_in_tokens, columns: %i[first_name last_name])
104
+ end
105
+
106
+ def check_approval
107
+ table_missing(
108
+ :bible270_comments,
109
+ columns: %i[approved moderated_at],
110
+ indexes: [{ columns: %i[approved day] }]
111
+ )
112
+ end
113
+
114
+ def check_settings
115
+ table_missing(
116
+ :bible270_settings,
117
+ columns: %i[id key value created_at updated_at],
118
+ indexes: [{ columns: %i[key], unique: true }]
119
+ )
120
+ end
121
+
122
+ def check_bible_version
123
+ table_missing(:bible270_readers, columns: %i[bible_version])
124
+ end
125
+
126
+ def check_checkoff_parts
127
+ table_missing(
128
+ :bible270_checkoffs,
129
+ columns: %i[part],
130
+ indexes: [{ columns: %i[reader_id day track part], unique: true }]
131
+ )
132
+ end
133
+
134
+ def check_remember_token
135
+ table_missing(
136
+ :bible270_readers,
137
+ columns: %i[remember_token],
138
+ indexes: [{ columns: %i[remember_token] }]
139
+ )
140
+ end
141
+
142
+ def check_mention_notices
143
+ table_missing(:bible270_readers, columns: %i[notify_on_mention])
144
+ end
145
+
146
+ def check_comment_parent
147
+ table_missing(
148
+ :bible270_comments,
149
+ columns: %i[parent_id],
150
+ indexes: [{ columns: %i[parent_id] }],
151
+ foreign_keys: [{ column: :parent_id, to_table: :bible270_comments }]
152
+ )
153
+ end
154
+
155
+ def check_likes
156
+ table_missing(
157
+ :bible270_likes,
158
+ columns: %i[id reader_id comment_id created_at updated_at],
159
+ indexes: [
160
+ { columns: %i[reader_id comment_id], unique: true },
161
+ { columns: %i[comment_id] }
162
+ ],
163
+ foreign_keys: [
164
+ { column: :reader_id, to_table: :bible270_readers },
165
+ { column: :comment_id, to_table: :bible270_comments }
166
+ ]
167
+ )
168
+ end
169
+
170
+ def check_blue_letter_bible
171
+ return [] if connection.column_exists?(:bible270_readers, :passage_source)
172
+
173
+ table_missing(:bible270_readers, columns: %i[blue_letter_bible])
174
+ end
175
+
176
+ def check_passage_source
177
+ table_missing(:bible270_readers, columns: %i[passage_source])
178
+ end
179
+
180
+ # The Bible270 installer can install Active Storage for reader avatars. A
181
+ # copied database can therefore have the same timestamp mismatch for Rails'
182
+ # generated Active Storage migration as it has for the engine migrations.
183
+ def check_active_storage
184
+ table_missing(
185
+ :active_storage_blobs,
186
+ columns: %i[id key filename content_type metadata service_name byte_size checksum created_at],
187
+ indexes: [{ columns: %i[key], unique: true }]
188
+ ) + table_missing(
189
+ :active_storage_attachments,
190
+ columns: %i[id name record_type record_id blob_id created_at],
191
+ indexes: [
192
+ { columns: %i[blob_id] },
193
+ { columns: %i[record_type record_id name blob_id], unique: true }
194
+ ],
195
+ foreign_keys: [{ column: :blob_id, to_table: :active_storage_blobs }]
196
+ ) + table_missing(
197
+ :active_storage_variant_records,
198
+ columns: %i[id blob_id variation_digest],
199
+ indexes: [{ columns: %i[blob_id variation_digest], unique: true }],
200
+ foreign_keys: [{ column: :blob_id, to_table: :active_storage_blobs }]
201
+ )
202
+ end
203
+
204
+ private
205
+
206
+ attr_reader :connection, :migration_context
207
+
208
+ def table_missing(table, columns:, indexes: [], foreign_keys: [])
209
+ return ["table #{table}"] unless connection.data_source_exists?(table)
210
+
211
+ missing_columns(table, columns) +
212
+ missing_indexes(table, indexes) +
213
+ missing_foreign_keys(table, foreign_keys)
214
+ end
215
+
216
+ def missing_columns(table, expected_columns)
217
+ existing = connection.columns(table).map { |column| column.name.to_s }
218
+ expected_columns.filter_map do |column|
219
+ "column #{table}.#{column}" unless existing.include?(column.to_s)
220
+ end
221
+ end
222
+
223
+ def missing_indexes(table, expected_indexes)
224
+ existing = connection.indexes(table)
225
+ expected_indexes.filter_map do |expected|
226
+ columns = expected.fetch(:columns).map(&:to_s)
227
+ next if existing.any? { |index| matching_index?(index, columns, expected) }
228
+
229
+ qualifier = expected[:unique] ? 'unique index' : 'index'
230
+ "#{qualifier} #{table}(#{columns.join(', ')})"
231
+ end
232
+ end
233
+
234
+ def matching_index?(index, columns, expected)
235
+ index.columns == columns && (!expected.key?(:unique) || index.unique == expected[:unique])
236
+ end
237
+
238
+ def missing_foreign_keys(table, expected_foreign_keys)
239
+ existing = connection.foreign_keys(table)
240
+ expected_foreign_keys.filter_map do |expected|
241
+ next if existing.any? { |foreign_key| matching_foreign_key?(foreign_key, expected) }
242
+
243
+ "foreign key #{table}.#{expected[:column]} -> #{expected[:to_table]}"
244
+ end
245
+ end
246
+
247
+ def matching_foreign_key?(foreign_key, expected)
248
+ foreign_key.column.to_s == expected.fetch(:column).to_s &&
249
+ foreign_key.to_table.to_s == expected.fetch(:to_table).to_s
250
+ end
251
+ end
252
+ end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'uri'
4
+ require 'bible270/versification'
5
+
3
6
  module Bible270
4
7
  # The translations a reader may choose between, and how each maps onto the
5
8
  # external reader's own code. Kept free of Rails so it can be unit tested.
@@ -11,14 +14,22 @@ module Bible270
11
14
  # `label` is the full name, for prose. `short` is what goes in the select,
12
15
  # where "New American Standard Bible 1995" overflows the box.
13
16
  VERSIONS = {
14
- 'NKJV' => { label: 'New King James Version', short: 'New King James Version', gateway: 'NKJV' },
15
- 'NASB95' => { label: 'New American Standard Bible 1995', short: 'New American Standard 1995', gateway: 'NASB1995' },
16
- 'LSB' => { label: 'Legacy Standard Bible', short: 'Legacy Standard Version', gateway: 'LSB' },
17
- 'ESV' => { label: 'English Standard Version', short: 'English Standard Version', gateway: 'ESV' },
18
- 'KJV' => { label: 'King James Version', short: 'King James Version', gateway: 'KJV' }
17
+ 'NKJV' => { label: 'New King James Version', short: 'New King James Version', gateway: 'NKJV', blue_letter: 'nkjv' },
18
+ 'NASB95' => { label: 'New American Standard Bible 1995', short: 'New American Standard 1995', gateway: 'NASB1995', blue_letter: 'nasb95' },
19
+ 'LSB' => { label: 'Legacy Standard Bible', short: 'Legacy Standard Version', gateway: 'LSB', blue_letter: 'lsb' },
20
+ 'ESV' => { label: 'English Standard Version', short: 'English Standard Version', gateway: 'ESV', blue_letter: 'esv' },
21
+ 'KJV' => { label: 'King James Version', short: 'King James Version', gateway: 'KJV', blue_letter: 'kjv' },
22
+ 'HEB/GRK' => { label: 'Hebrew and Greek', short: 'Hebrew and Greek', gateway: 'WLC', blue_letter: 'wlc/mgnt' }
19
23
  }.freeze
20
24
 
21
25
  DEFAULT = 'NKJV'
26
+ ORIGINAL_LANGUAGES = 'HEB/GRK'
27
+ BLUE_LETTER_BASE_URL = 'https://www.blueletterbible.org'
28
+ BLUE_LETTER_BOOK_CODES = %w[
29
+ gen exo lev num deu jos jdg rut 1sa 2sa 1ki 2ki 1ch 2ch ezr neh est job psa pro ecc sng
30
+ isa jer lam eze dan hos joe amo oba jon mic nah hab zep hag zec mal mat mar luk joh act rom
31
+ 1co 2co gal eph php col 1th 2th 1ti 2ti tit phm heb jas 1pe 2pe 1jo 2jo 3jo jud rev
32
+ ].freeze
22
33
 
23
34
  module_function
24
35
 
@@ -57,6 +68,51 @@ module Bible270
57
68
  codes.map { |code| ["#{code} — #{short_label(code)}", code] }
58
69
  end
59
70
 
71
+ def passage_url(reference, version, blue_letter: false)
72
+ if blue_letter || normalize(version) == ORIGINAL_LANGUAGES
73
+ return blue_letter_url(reference, version)
74
+ end
75
+
76
+ search = URI.encode_www_form_component(reference)
77
+ gateway = URI.encode_www_form_component(gateway_code(version))
78
+ "https://www.biblegateway.com/passage/?search=#{search}&version=#{gateway}"
79
+ end
80
+
81
+ # Blue Letter Bible identifies a verse with its canonical chapter number times
82
+ # 1,000 plus the verse number: Genesis 1:1 is 1001 and Matthew 1:1 is 930001.
83
+ # A plan reference can span chapters or books; BLB has no equivalent passage
84
+ # query URL, so link to the first chapter/verse and let its chapter navigation
85
+ # carry the reader onward.
86
+ def blue_letter_url(reference, version = ORIGINAL_LANGUAGES)
87
+ books = Versification::VERSES.keys
88
+ book_index = books.index { |book| reference.to_s.start_with?("#{book} ") }
89
+ return "#{BLUE_LETTER_BASE_URL}/" unless book_index
90
+
91
+ book = books[book_index]
92
+ location = reference.to_s.delete_prefix("#{book} ")
93
+ match = location.match(%r{\A(\d+)(?::(\d+))?})
94
+ return "#{BLUE_LETTER_BASE_URL}/" unless match
95
+
96
+ chapter = match[1].to_i
97
+ verse = match[2]&.to_i || 1
98
+ chapter_verses = Versification::VERSES[book]
99
+ return "#{BLUE_LETTER_BASE_URL}/" unless chapter.between?(1, chapter_verses.length)
100
+ return "#{BLUE_LETTER_BASE_URL}/" unless verse.between?(1, chapter_verses[chapter - 1])
101
+
102
+ canonical_chapter = books.first(book_index).sum { |prior| Versification::VERSES[prior].length } + chapter
103
+ new_testament = book_index >= books.index('Matthew')
104
+ reader = if normalize(version) == ORIGINAL_LANGUAGES
105
+ new_testament ? 'mgnt' : 'wlc'
106
+ else
107
+ VERSIONS.dig(normalize(version), :blue_letter)
108
+ end
109
+ return "#{BLUE_LETTER_BASE_URL}/" if reader.to_s.empty?
110
+
111
+ anchor = (canonical_chapter * 1000) + verse
112
+ "#{BLUE_LETTER_BASE_URL}/#{reader}/#{BLUE_LETTER_BOOK_CODES[book_index]}/" \
113
+ "#{chapter}/#{verse}/s_#{anchor}"
114
+ end
115
+
60
116
  # Falls back to the configured default, then to ESV, so a link is never built
61
117
  # with a blank version.
62
118
  def resolve(code)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bible270
4
- VERSION = '0.17.2'
4
+ VERSION = '0.18.1'
5
5
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bible270/migration_reconciler'
4
+
5
+ namespace :bible270 do
6
+ namespace :migrations do
7
+ desc 'Reconcile copied Bible270/Active Storage tables with schema_migrations (set APPLY=1 to write)'
8
+ task reconcile: :environment do
9
+ reconciler = Bible270::MigrationReconciler.for_current_database
10
+ statuses = reconciler.statuses
11
+
12
+ if statuses.empty?
13
+ puts 'No pending Bible270 or related Active Storage migrations were found.'
14
+ next
15
+ end
16
+
17
+ complete, incomplete = statuses.partition(&:complete?)
18
+
19
+ puts 'Already reflected in the database schema:'
20
+ if complete.empty?
21
+ puts ' (none)'
22
+ else
23
+ complete.each do |status|
24
+ puts " #{status.migration.version} #{status.migration.name}"
25
+ end
26
+ end
27
+
28
+ unless incomplete.empty?
29
+ puts
30
+ puts 'Still missing schema changes (these will remain pending):'
31
+ incomplete.each do |status|
32
+ puts " #{status.migration.version} #{status.migration.name}"
33
+ status.missing.each { |item| puts " - #{item}" }
34
+ end
35
+ end
36
+
37
+ puts
38
+ unless ENV['APPLY'] == '1'
39
+ puts 'Preview only; schema_migrations was not changed.'
40
+ puts 'Review the list, back up the database, then rerun with APPLY=1.'
41
+ next
42
+ end
43
+
44
+ if complete.empty?
45
+ puts 'Nothing was safe to mark as applied.'
46
+ next
47
+ end
48
+
49
+ ActiveRecord::Base.transaction do
50
+ complete.each { |status| reconciler.mark_applied!(status) }
51
+ end
52
+
53
+ puts "Marked #{complete.size} verified #{'migration'.pluralize(complete.size)} as applied."
54
+ puts 'Run bin/rails db:migrate to apply the migrations whose schema changes are still missing.'
55
+ end
56
+ end
57
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bible270
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.2
4
+ version: 0.18.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew vonderLuft
@@ -136,6 +136,7 @@ files:
136
136
  - app/views/bible270/shared/_footer.html.erb
137
137
  - app/views/bible270/shared/_grid_legend.html.erb
138
138
  - app/views/bible270/shared/_header.html.erb
139
+ - app/views/bible270/shared/_passage_source.html.erb
139
140
  - app/views/bible270/shared/_progress.html.erb
140
141
  - app/views/bible270/shared/_reflections.html.erb
141
142
  - app/views/bible270/shared/_styles.html.erb
@@ -157,6 +158,8 @@ files:
157
158
  - db/migrate/20260101000011_add_mention_notices_to_bible270_readers.rb
158
159
  - db/migrate/20260101000012_add_parent_to_bible270_comments.rb
159
160
  - db/migrate/20260101000013_create_bible270_likes.rb
161
+ - db/migrate/20260101000014_add_blue_letter_bible_to_bible270_readers.rb
162
+ - db/migrate/20260101000015_add_passage_source_to_bible270_readers.rb
160
163
  - lib/bible270.rb
161
164
  - lib/bible270/avatars.rb
162
165
  - lib/bible270/configuration.rb
@@ -164,6 +167,7 @@ files:
164
167
  - lib/bible270/engine.rb
165
168
  - lib/bible270/favicon.rb
166
169
  - lib/bible270/mentions.rb
170
+ - lib/bible270/migration_reconciler.rb
167
171
  - lib/bible270/names.rb
168
172
  - lib/bible270/plan.rb
169
173
  - lib/bible270/translations.rb
@@ -172,6 +176,7 @@ files:
172
176
  - lib/generators/bible270/install/install_generator.rb
173
177
  - lib/generators/bible270/install/templates/bible270.rb.tt
174
178
  - lib/generators/bible270/install/templates/omniauth.rb.tt
179
+ - lib/tasks/bible270_migrations.rake
175
180
  homepage: https://github.com/avonderluft/bible270
176
181
  licenses:
177
182
  - MIT