rails_onboarding 0.8.13 → 0.8.15

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: 593e5e47988a8b3f81d6ea034f186669a099d98b09707723ed723128bcdad01d
4
- data.tar.gz: 790f85934b3f9573cc6421488b882fcf3ac80340e27a0b6b587cc2de93bcdcab
3
+ metadata.gz: df859b74947576d40a44a8a6ce1e37ceb1c9b92297875ac06faa3926f19489cc
4
+ data.tar.gz: 360a0d59bece71c30df19f549bea51cba322b7d0abf0e657d2e77d8b4b9e9d3b
5
5
  SHA512:
6
- metadata.gz: f9bc4b4bd26ace6d84e153b906a0b687035e6d0ccb1b81d13ed15f6219bcc1e7be6c02ae30aa85658fc3c79933c5a7c374243bdffc9bf0197b923172ce1c2a36
7
- data.tar.gz: 1d1bed61cc8462db8e23fe1e92a114d7df9be93cc7254e6f0cecfe8d56a8fcae765ffe072769c768044f5315795510c0c9533339f9835ac57fc48213a6f83e77
6
+ metadata.gz: 0fce05257f5c3f4af31cd191dc961ea151e2b753988758e53315e9914f6db67d4d22f1759ae81562b60b8bbe177a2cd3b59ddeb0f840f42ff46b0834f32b63fe
7
+ data.tar.gz: 83404bc6ac100510474a792a1ff245dbb09f53166df47325117b4f3551cacdeca9bf60dc54550d6c07794c8f371a93f9c2779dc1c48d6d1ffae01d0c4a9ef16e
@@ -8,6 +8,9 @@
8
8
  max-width: 500px;
9
9
  width: 100%;
10
10
  pointer-events: none;
11
+ /* Outside .onboarding-container, which is where the engine sets its font, so it would otherwise
12
+ take the browser's serif default. */
13
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
11
14
  }
12
15
 
13
16
  .onboarding-flash {
@@ -29,25 +32,46 @@
29
32
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
30
33
  }
31
34
 
32
- /* Flash types */
35
+ /* Each message is an open <dialog> (see shared/_flash), so it closes without JavaScript. Undo
36
+ what the browser gives a dialog - centred, absolutely placed, bordered all round - leaving the
37
+ coloured left border from the type rules below. */
38
+ dialog.onboarding-flash {
39
+ inset: auto;
40
+ margin: 0 0 12px;
41
+ width: auto;
42
+ max-width: none;
43
+ max-height: none;
44
+ border-top: 0;
45
+ border-right: 0;
46
+ border-bottom: 0;
47
+ }
48
+
49
+ /* The close form is only there for method="dialog"; its button is the flex item. */
50
+ .onboarding-flash form {
51
+ display: contents;
52
+ }
53
+
54
+ /* Flash types. Each pair is at least 4.5:1 (WCAG AA): white on the 500-weight greens, ambers and
55
+ reds these used was 2-4:1, unreadable on amber in particular. Amber keeps its colour and takes
56
+ dark text instead. */
33
57
  .flash-notice,
34
58
  .flash-success {
35
- background-color: #10b981;
59
+ background-color: #047857;
36
60
  color: white;
37
- border-left: 4px solid #059669;
61
+ border-left: 4px solid #065f46;
38
62
  }
39
63
 
40
64
  .flash-alert,
41
65
  .flash-warning {
42
66
  background-color: #f59e0b;
43
- color: white;
44
- border-left: 4px solid #d97706;
67
+ color: #451a03;
68
+ border-left: 4px solid #b45309;
45
69
  }
46
70
 
47
71
  .flash-error {
48
- background-color: #ef4444;
72
+ background-color: #b91c1c;
49
73
  color: white;
50
- border-left: 4px solid #dc2626;
74
+ border-left: 4px solid #7f1d1d;
51
75
  }
52
76
 
53
77
  .flash-info {
@@ -152,6 +176,9 @@
152
176
  right: 10px;
153
177
  left: 10px;
154
178
  max-width: none;
179
+ /* Not 100%: with both left and right set, a width overrides right, and the message ran 10px
180
+ off the screen. */
181
+ width: auto;
155
182
  }
156
183
 
157
184
  .onboarding-flash {
@@ -26,7 +26,14 @@ module RailsOnboarding
26
26
  included do
27
27
  # Store A/B test assignments as JSON
28
28
  # Expected column: ab_test_assignments (jsonb or text)
29
- serialize :ab_test_assignments, coder: JSON unless column_names.include?("ab_test_assignments") && columns_hash["ab_test_assignments"].type == :jsonb
29
+ # column_names and columns_hash both query the database, and this runs
30
+ # when the host model is loaded -- possibly before the table exists.
31
+ # See RailsOnboarding::SchemaGuard.
32
+ if RailsOnboarding::SchemaGuard.columns_queryable?(self)
33
+ unless column_names.include?("ab_test_assignments") && columns_hash["ab_test_assignments"].type == :jsonb
34
+ serialize :ab_test_assignments, coder: JSON
35
+ end
36
+ end
30
37
 
31
38
  # Callbacks
32
39
  after_initialize :assign_ab_test_variants, if: :new_record?
@@ -7,43 +7,10 @@ module RailsOnboarding
7
7
  MAX_MILESTONES_ACHIEVED = 500
8
8
  MAX_JSON_SIZE_BYTES = 65_535 # ~64KB for TEXT columns
9
9
 
10
- # Whether the host model's table can be inspected right now.
11
- #
12
- # `columns_hash` and `column_names` both query the database, and the
13
- # `included do` block below runs the moment the host model is loaded --
14
- # which can be long before that table exists. A host that references its
15
- # user model from an initializer hits this immediately: the common OmniAuth
16
- # `:identity` setup passes `model: User`, so the model loads during boot,
17
- # the include queries `columns_hash`, and against an empty database the
18
- # whole process dies with "Table 'users' doesn't exist".
19
- #
20
- # That is circular and unrecoverable: loading the schema requires booting
21
- # the app, and booting the app requires the schema. It stops `db:prepare`
22
- # bootstrapping a genuinely new database, a restore into an empty schema,
23
- # and CI against a fresh database service.
24
- #
25
- # A missing table, a missing database and an absent connection all mean the
26
- # same thing here -- the column types cannot be known yet -- so degrade
27
- # instead of raising. Nothing is lost: a process that boots against an empty
28
- # database is bootstrapping it, not serving from it, and the next process
29
- # configures everything normally once the schema is in place.
30
- #
31
- # Only ever answers false when the table is positively known to be absent.
32
- # This concern is deliberately includable into plain classes that are not
33
- # ActiveRecord models -- hence the `respond_to?(:has_many)` and
34
- # `respond_to?(:validate)` guards below -- and such a class has no
35
- # `table_exists?` while still answering `columns_hash`. Saying "not
36
- # available" for those would silently stop configuring them, so anything
37
- # that cannot be asked is treated as queryable and left to the callers'
38
- # own guards, exactly as before this check existed.
10
+ # Whether the host model's columns can be read yet. See
11
+ # RailsOnboarding::SchemaGuard, which three concerns share.
39
12
  def self.columns_queryable?(model)
40
- return true unless model.respond_to?(:table_exists?)
41
-
42
- model.table_exists?
43
- rescue ActiveRecord::NoDatabaseError,
44
- ActiveRecord::ConnectionNotEstablished,
45
- ActiveRecord::StatementInvalid
46
- false
13
+ SchemaGuard.columns_queryable?(model)
47
14
  end
48
15
 
49
16
  included do
@@ -62,8 +29,8 @@ module RailsOnboarding
62
29
 
63
30
  # Fix for Rails 8: Use the new serialize syntax.
64
31
  # Guarded because columns_hash queries the database -- see
65
- # Onboardable.columns_queryable? above.
66
- if RailsOnboarding::Onboardable.columns_queryable?(self)
32
+ # RailsOnboarding::SchemaGuard.
33
+ if RailsOnboarding::SchemaGuard.columns_queryable?(self)
67
34
  if columns_hash["feature_tooltips_shown"]&.type == :text
68
35
  serialize :feature_tooltips_shown, coder: JSON
69
36
  end
@@ -99,7 +66,7 @@ module RailsOnboarding
99
66
  # than raising on a column that isn't there.
100
67
  def onboarding_replay_supported?
101
68
  return false unless respond_to?(:column_names)
102
- return false unless RailsOnboarding::Onboardable.columns_queryable?(self)
69
+ return false unless RailsOnboarding::SchemaGuard.columns_queryable?(self)
103
70
 
104
71
  column_names.include?("onboarding_replay_started_at") &&
105
72
  column_names.include?("onboarding_replay_steps")
@@ -763,8 +730,18 @@ module RailsOnboarding
763
730
  { onboarding_replay_started_at: nil, onboarding_replay_steps: [] }
764
731
  end
765
732
 
733
+ # column_names queries the database like the rest, so it takes the same
734
+ # guard -- but purely for symmetry: this one cannot actually be reached
735
+ # with the table missing. It is an instance method, and ActiveRecord cannot
736
+ # instantiate a model whose table is absent (`new` raises while building
737
+ # attribute types), so the guard can never fire. Kept so every column query
738
+ # in this file reads the same way, and it costs nothing: table_exists?
739
+ # reads the schema cache.
766
740
  def onboarding_column?(name)
767
- self.class.respond_to?(:column_names) && self.class.column_names.include?(name)
741
+ return false unless self.class.respond_to?(:column_names)
742
+ return false unless RailsOnboarding::SchemaGuard.columns_queryable?(self.class)
743
+
744
+ self.class.column_names.include?(name)
768
745
  end
769
746
 
770
747
  def persist_and_track!(attributes = nil)
@@ -36,7 +36,14 @@ module RailsOnboarding
36
36
  included do
37
37
  # Store revealed features as JSON
38
38
  # Expected column: revealed_features (jsonb or text)
39
- serialize :revealed_features, coder: JSON unless column_names.include?("revealed_features") && columns_hash["revealed_features"].type == :jsonb
39
+ # column_names and columns_hash both query the database, and this runs
40
+ # when the host model is loaded -- possibly before the table exists.
41
+ # See RailsOnboarding::SchemaGuard.
42
+ if RailsOnboarding::SchemaGuard.columns_queryable?(self)
43
+ unless column_names.include?("revealed_features") && columns_hash["revealed_features"].type == :jsonb
44
+ serialize :revealed_features, coder: JSON
45
+ end
46
+ end
40
47
 
41
48
  # Callback to check and reveal features
42
49
  after_save :check_progressive_features, if: :saved_change_to_onboarding_current_step?
@@ -0,0 +1,38 @@
1
+ module RailsOnboarding
2
+ # Whether a host model's columns can be read yet.
3
+ #
4
+ # Three concerns inspect columns the moment they are included -- to decide
5
+ # whether to configure JSON serialization -- and `columns_hash` and
6
+ # `column_names` both query the database. That runs when the host model is
7
+ # loaded, which can be long before the table exists.
8
+ #
9
+ # A host that references its user model from an initializer hits this
10
+ # immediately. The common OmniAuth `:identity` setup passes `model: User`, so
11
+ # the model loads during boot and the process dies with
12
+ # `Table 'users' doesn't exist`. That is circular and unrecoverable: loading
13
+ # the schema requires booting the app, and booting the app requires the
14
+ # schema. It stops `db:prepare` bootstrapping a genuinely new database, a
15
+ # restore into an empty schema, and CI against a fresh database service.
16
+ module SchemaGuard
17
+ module_function
18
+
19
+ # Only ever answers false when the table is positively known to be absent.
20
+ #
21
+ # These concerns are deliberately includable into plain classes that are not
22
+ # ActiveRecord models -- hence the `respond_to?(:has_many)` and
23
+ # `respond_to?(:validate)` guards in Onboardable -- and such a class answers
24
+ # `columns_hash` while having no `table_exists?`. Vetoing those would
25
+ # silently stop configuring them, so anything that cannot be asked is
26
+ # treated as queryable and left to the callers' own guards, exactly as
27
+ # before this check existed.
28
+ def columns_queryable?(model)
29
+ return true unless model.respond_to?(:table_exists?)
30
+
31
+ model.table_exists?
32
+ rescue ActiveRecord::NoDatabaseError,
33
+ ActiveRecord::ConnectionNotEstablished,
34
+ ActiveRecord::StatementInvalid
35
+ false
36
+ end
37
+ end
38
+ end
@@ -51,6 +51,11 @@
51
51
  </head>
52
52
  <body>
53
53
 
54
+ <%# Without this, a message on the way to onboarding was lost: a host's redirect_to with an alert,
55
+ the engine's own redirect_to onboarding_path, alert:, and the Turbo Stream errors, which replace
56
+ #flash-messages and found nothing to replace. Fixed-position, so it sits over the page. %>
57
+ <%= render "rails_onboarding/shared/flash" %>
58
+
54
59
  <%# .onboarding-container/.onboarding-content provide the page-level
55
60
  min-height/centering/responsive padding that the stylesheets (see
56
61
  application.css and mobile.css) already define for every onboarding
@@ -1,25 +1,26 @@
1
- <div id="flash-messages" class="flash-container">
2
- <% if defined?(notice) && notice %>
3
- <div class="onboarding-flash flash-notice" role="alert">
4
- <span class="flash-icon">✓</span>
5
- <span class="flash-message"><%= notice %></span>
6
- <button class="flash-close" onclick="this.parentElement.remove()" aria-label="Dismiss">×</button>
7
- </div>
8
- <% end %>
1
+ <%# The flash, on every page of the engine layout, and the target the onboarding controller's
2
+ Turbo Stream errors replace (#flash-messages). Each message comes from a local when one is
3
+ given - those streams pass the message they want shown - and from the flash otherwise.
9
4
 
10
- <% if defined?(alert) && alert %>
11
- <div class="onboarding-flash flash-alert" role="alert">
12
- <span class="flash-icon">⚠</span>
13
- <span class="flash-message"><%= alert %></span>
14
- <button class="flash-close" onclick="this.parentElement.remove()" aria-label="Dismiss">×</button>
15
- </div>
16
- <% end %>
17
-
18
- <% if defined?(error) && error %>
19
- <div class="onboarding-flash flash-error" role="alert">
20
- <span class="flash-icon">✕</span>
21
- <span class="flash-message"><%= error %></span>
22
- <button class="flash-close" onclick="this.parentElement.remove()" aria-label="Dismiss">×</button>
23
- </div>
5
+ Each message is an open <dialog> closed by a form with method="dialog", which the browser
6
+ handles itself: no JavaScript, so nothing for a strict Content Security Policy to refuse. The
7
+ close buttons were onclick= attributes, which such a policy blocks without a word. Turbo
8
+ leaves method="dialog" forms alone. %>
9
+ <% messages = {
10
+ notice: local_assigns.fetch(:notice) { flash[:notice] },
11
+ alert: local_assigns.fetch(:alert) { flash[:alert] },
12
+ error: local_assigns.fetch(:error) { flash[:error] }
13
+ } %>
14
+ <% icons = { notice: "✓", alert: "⚠", error: "✕" } %>
15
+ <div id="flash-messages" class="flash-container">
16
+ <% messages.each do |type, message| %>
17
+ <% next if message.blank? %>
18
+ <dialog open class="onboarding-flash flash-<%= type %>">
19
+ <span class="flash-icon" aria-hidden="true"><%= icons[type] %></span>
20
+ <span class="flash-message" role="alert"><%= message %></span>
21
+ <form method="dialog">
22
+ <button class="flash-close" aria-label="Dismiss">×</button>
23
+ </form>
24
+ </dialog>
24
25
  <% end %>
25
26
  </div>
@@ -1,3 +1,3 @@
1
1
  module RailsOnboarding
2
- VERSION = "0.8.13"
2
+ VERSION = "0.8.15"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_onboarding
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.13
4
+ version: 0.8.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Lewis
@@ -183,6 +183,7 @@ files:
183
183
  - app/models/concerns/rails_onboarding/onboardable.rb
184
184
  - app/models/concerns/rails_onboarding/personalizable.rb
185
185
  - app/models/concerns/rails_onboarding/progressive_disclosure.rb
186
+ - app/models/concerns/rails_onboarding/schema_guard.rb
186
187
  - app/models/rails_onboarding/analytics.rb
187
188
  - app/models/rails_onboarding/analytics_event.rb
188
189
  - app/models/rails_onboarding/application_record.rb
@@ -282,7 +283,7 @@ licenses:
282
283
  metadata:
283
284
  allowed_push_host: https://rubygems.org
284
285
  homepage_uri: https://github.com/bunnahabhain/rails_onboarding
285
- source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.13
286
+ source_code_uri: https://github.com/bunnahabhain/rails_onboarding/tree/v0.8.15
286
287
  changelog_uri: https://github.com/bunnahabhain/rails_onboarding/blob/master/docs/CHANGELOG.md
287
288
  rdoc_options: []
288
289
  require_paths:
@@ -298,7 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
298
299
  - !ruby/object:Gem::Version
299
300
  version: '0'
300
301
  requirements: []
301
- rubygems_version: 4.0.19
302
+ rubygems_version: 4.0.22
302
303
  specification_version: 4
303
304
  summary: Flexible onboarding engine for Rails applications
304
305
  test_files: []