action_version_preview 0.1.0 → 0.2.0

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: e6d263561ee95f415d87df090bb1e0008500048a1a896c96ae2df8ba7690e6b7
4
- data.tar.gz: fc72870345f275e9e40fe4d75a5bba496467239e577fb524ecfd2e4b9b740fb2
3
+ metadata.gz: e06ec511eedee6043f22f30770eba50bbaaf4e084607fa22aefc3dd0bec1c590
4
+ data.tar.gz: c0b5c97188fc3e16d3a68d2df693198f96957f035fc565af62b07cac569d153e
5
5
  SHA512:
6
- metadata.gz: f8353f9edc0f76a49425d6a5147b814fc6ca82304c465c186f33a94a8c42f61cea4d8120c0f8c5332636f277705e43e3755f1d38b10706137902d356d5c98011
7
- data.tar.gz: 87099d11c6a79710263dc7c5b8f58a0e91a066279f60a9f4ad8b2c2a011096f1e33b79db414719b61a237e4f755b0c3950dedc77dd48272135b5138a03e7af80
6
+ metadata.gz: c4a6ecab2cdcff048f1de1181d2924f6a5f7531fa20855a0c2af6fbeafd632e394e385fe34d95f5e447bb5e65cf30215f1cf1adf837a39b94ec60e1945e21b00
7
+ data.tar.gz: '089c2fb8619a595af161f68c3f5e73f1ce16cd0df053c6b53320bebfb6954764a86608efe93b944e2a030c68031f453198974c0c0279a7584e6009e967d80b21'
data/CHANGELOG.md ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ ## [0.2.0] - 2026-09-15
6
+
7
+ ### Fixed
8
+
9
+ - Fixed `ActionVersionPreview::SwitcherHelper` load-order errors during Rails boot
10
+ and asset precompilation, including early controller/view loading.
11
+ - Kept switcher links on the current path, preserving mounted prefixes, nested
12
+ query filters, and configured preview parameter names. Query keys are no longer
13
+ interpreted as Rails routing options.
14
+ - Excluded POST body fields from switcher URLs.
15
+
16
+ ### Added
17
+
18
+ - Fresh-process regression tests for early/lazy framework loading, development
19
+ reloading, and production asset precompilation using a built and installed gem.
20
+ - Integration tests for switcher URL safety, mounted paths, custom parameters,
21
+ nested filters, and POST-body exclusion.
22
+ - Comprehensive public usage documentation and upgrade instructions, included
23
+ with the gem alongside this changelog.
24
+
25
+ ### Changed
26
+
27
+ - Explicitly load the stateless switcher helper from `lib` so its availability does
28
+ not depend on Rails autoloading or reloading.
29
+ - Updated development dependency resolution and GitHub Actions versions.
30
+ - Clarified supported integration behavior and current discovery limitations.
31
+
32
+ No migrations or required configuration changes. Declared Ruby/Rails requirements,
33
+ preview access defaults, and Default-link behavior are unchanged. Full details are
34
+ in the [0.2.0 release notes](docs/releases/0.2.0.md).
35
+
36
+ ## [0.1.0] - 2025-12-17
37
+
38
+ ### Added
39
+
40
+ - Initial release.
41
+ - Side-by-side UI previews in separate tabs using Rails view variants.
42
+ - Automatic integration with `ActionController::Base`.
43
+ - Optional variant switcher and configurable preview parameter/access check.
44
+ - Declared support for Rails 7.0 through 8.x.
data/README.md CHANGED
@@ -1,118 +1,136 @@
1
1
  # ActionVersionPreview
2
2
 
3
- Preview multiple versions of your UI simultaneously using Rails' built-in view variants. No feature flags, no complex setup. Just create variant templates and visit them via URL.
3
+ Preview multiple versions of a Rails UI in separate browser tabs using native
4
+ view variants. Add variant templates, open their URLs, and compare designs while
5
+ signed in as the same user. No additional database, feature-flag service, or
6
+ JavaScript is required.
4
7
 
5
- ## Why?
8
+ [Full usage guide](docs/usage.md) · [Changelog](CHANGELOG.md) · [RubyGems](https://rubygems.org/gems/action_version_preview)
6
9
 
7
- Feature flag gems like Flipper are designed for toggling features on/off for users, not for showing multiple versions at once. For design iterations and feedback collection, you want to open three browser tabs side by side, each showing a different version, all logged in as the same user.
10
+ ## Install
8
11
 
9
- ActionVersionPreview makes this trivial using Rails' native view variants.
10
-
11
- <img width="2146" height="2014" alt="image" src="https://github.com/user-attachments/assets/c39b1305-ad14-4958-be39-d714d53d1990" />
12
-
13
- ## Installation
14
-
15
- Add to your Gemfile:
12
+ Add the gem to your Rails application's Gemfile:
16
13
 
17
14
  ```ruby
18
- gem "action_version_preview"
15
+ gem "action_version_preview", "~> 0.2.0"
19
16
  ```
20
17
 
21
- Run `bundle install`. That's it. The concern is automatically included in all controllers.
18
+ Run `bundle install` and restart the application. No generator or engine mount is
19
+ needed. Preview selection is added to controllers inheriting from
20
+ `ActionController::Base`.
22
21
 
23
- ## Usage
22
+ ## Quick start
24
23
 
25
- ### 1. Create Variant Templates
24
+ ### 1. Create variant templates
26
25
 
27
- Rails looks for variant templates using this naming convention:
26
+ For a `DashboardController#show` action, keep a default template and add variants:
28
27
 
29
- ```
30
- app/views/dashboard/show.html.erb # default
31
- app/views/dashboard/show.html+v2.erb # variant :v2
32
- app/views/dashboard/show.html+redesign.erb # variant :redesign
28
+ ```text
29
+ app/views/dashboard/show.html.erb
30
+ app/views/dashboard/show.html+v2.erb
31
+ app/views/dashboard/show.html+redesign.erb
33
32
  ```
34
33
 
35
- This works for layouts, partials, mailers, and ViewComponent templates too.
34
+ Use names such as `v2`, `redesign`, or `new_layout` for automatic switcher discovery.
36
35
 
37
- ### 2. Visit the Variant URL
36
+ ### 2. Open the preview URLs
38
37
 
39
- ```
40
- /dashboard # renders show.html.erb
41
- /dashboard?vv=v2 # renders show.html+v2.erb
42
- /dashboard?vv=true # renders default, but activates the switcher
43
- ```
38
+ In development or test:
39
+
40
+ | URL | Result |
41
+ | --- | --- |
42
+ | `/dashboard` | Normal rendering |
43
+ | `/dashboard?vv=v2` | Selects the `v2` variant |
44
+ | `/dashboard?vv=redesign` | Selects the `redesign` variant |
45
+ | `/dashboard?vv=true` | Enables the switcher without selecting a variant |
44
46
 
45
- ### 3. Add the Variant Switcher (Optional)
47
+ The last example keeps any variant your application already set, such as a mobile
48
+ variant. Otherwise it renders the default template.
46
49
 
47
- Drop the built-in switcher widget in your layout:
50
+ ### 3. Add the optional switcher
51
+
52
+ Put this near the end of your application layout's body:
48
53
 
49
54
  ```erb
50
55
  <%= variant_switcher %>
51
56
  ```
52
57
 
53
- The switcher automatically detects available variants by scanning the view directory for `+variant` template files. It only appears when:
54
- - The `vv` param is present in the URL (e.g., `?vv=true` or `?vv=v2`)
55
- - The current action has variant templates available
56
- - The user can preview variants (dev/test by default)
58
+ The switcher appears only when preview mode is requested, access is allowed, and
59
+ variant templates are discovered for the current action. Its links preserve the
60
+ current path, mounted prefix, and query filters. **Default** removes the preview
61
+ parameter and closes the switcher. Add `?vv=true` again to reopen it.
57
62
 
58
- Standard Rails variants (`mobile`, `tablet`, `phone`, `desktop`) are excluded from detection.
63
+ Automatic discovery in 0.2.0 covers HTML ERB action templates in the host app's
64
+ `app/views/<controller_path>` directory. See the [discovery limits](docs/usage.md#template-discovery)
65
+ for layouts, partials, other handlers, and engine views.
59
66
 
60
- ## Configuration
67
+ ## Enable previews for your users
61
68
 
62
- Zero config is the default. But if you need to customize:
69
+ By default, previews are enabled only in development and test. Production,
70
+ staging, and other environments deny preview selection until you configure access.
63
71
 
64
72
  ```ruby
65
73
  # config/initializers/action_version_preview.rb
66
74
  ActionVersionPreview.configure do |config|
67
- # Change the URL parameter (default: :vv)
68
- config.param_name = :v
69
-
70
- # Control who can preview variants (default: dev/test only)
71
- # In production, you might want admins only:
75
+ config.param_name = :vv # Optional; a string also works.
72
76
  config.access_check = ->(controller) {
73
- Rails.env.development? ||
74
- Rails.env.test? ||
75
- controller.current_user&.admin?
77
+ Rails.env.development? || Rails.env.test? ||
78
+ (controller.respond_to?(:current_user, true) &&
79
+ controller.send(:current_user)&.admin?)
76
80
  }
77
81
  end
78
82
  ```
79
83
 
80
- ## Helper Methods
84
+ Adapt `current_user` and `admin?` to your authentication system. The example also
85
+ works when `current_user` is private. Your user/context must be available when the
86
+ preview callback runs; see [access control and callback order](docs/usage.md#access-control-and-callback-order).
87
+
88
+ The preview parameter applies to one request. Links and redirects elsewhere in
89
+ your app do not inherit it automatically. Ordinary application authorization still
90
+ controls access to data and actions.
81
91
 
82
- These are available in controllers and views:
92
+ ## What's new in 0.2.0
83
93
 
84
- | Method | Description |
85
- |--------|-------------|
86
- | `current_variant` | Returns the active variant symbol (e.g., `:v2`) or `nil` |
87
- | `detected_variants` | Returns array of variant names found for current action |
88
- | `variant_preview_active?` | Returns true if variant preview mode is active |
89
- | `can_preview_variants?` | Returns true if current user can access variants |
90
- | `variant_switcher` | Renders the switcher widget |
94
+ - Fixes `ActionVersionPreview::SwitcherHelper` load-order errors during application
95
+ boot and asset precompilation, including early controller/view loading.
96
+ - Keeps switcher links on the current request path. Query keys such as `host` and
97
+ `protocol` remain query data; POST body fields are not copied into URLs.
98
+ - Adds boot, reload, packaged-gem precompilation, and switcher URL regression tests.
91
99
 
92
- ## How It Works
100
+ To upgrade from 0.1.0, update your Gemfile constraint if necessary, run
101
+ `bundle update action_version_preview`, and rebuild/restart the application.
102
+ No migration or configuration change is required. Read the [0.2.0 release notes](docs/releases/0.2.0.md).
93
103
 
94
- Under the hood, ActionVersionPreview sets `request.variant` based on the URL parameter. Rails' template resolver then automatically looks for matching variant templates.
104
+ ## Compatibility
95
105
 
96
- When you visit `/dashboard?vv=v2`:
97
- 1. The `before_action` extracts `vv=v2` from params
98
- 2. It sets `request.variant = :v2`
99
- 3. Rails renders `show.html+v2.erb` instead of `show.html.erb`
100
- 4. The switcher detects all `show.html+*.erb` variants in the view directory
106
+ - Declared requirements: Ruby **3.1+**, Rails **7.0 or later, below 9.0**.
107
+ - Each Rails version may require a newer Ruby; the reviewed Rails 8.1 bundle
108
+ requires Ruby 3.2+.
109
+ - Release validation used Ruby 4.0.5 with Rails 8.1.3.1. CI is configured for Ruby
110
+ 3.4.7. A full matrix of the declared versions is not yet in place.
111
+ - Automatic controller integration targets `ActionController::Base`, not API-only
112
+ controllers inheriting directly from `ActionController::API`.
113
+
114
+ ## Development
115
+
116
+ ```sh
117
+ bundle install
118
+ bin/rails test
119
+ bin/rubocop
120
+ RAILS_ENV=test bundle exec rake app:zeitwerk:check
121
+ bundle exec rake build
122
+ ```
101
123
 
102
- ## Comparison: Feature Flags vs View Variants
124
+ The test suite includes an isolated host that installs the built gem and compiles
125
+ assets in production mode. See the [release procedure](docs/releasing.md) for
126
+ security scans and publication.
103
127
 
104
- | Feature Flags (Flipper) | View Variants (This Gem) |
105
- |------------------------|--------------------------|
106
- | Toggle features on/off for users | Access all versions simultaneously |
107
- | One version "live" at a time | Side-by-side comparison in multiple tabs |
108
- | Percentage rollouts, A/B testing | Design iteration, feedback collection |
109
- | Requires database/Redis | Zero dependencies |
128
+ ## Background
110
129
 
111
- ## Requirements
130
+ [Why I built design previews for Rails](https://code.avi.nyc/design-previews-for-ruby-on-rails)
112
131
 
113
- - Rails 7.0+ or 8.x
114
- - Ruby 3.1+
132
+ <img width="2146" height="2014" alt="Several Rails UI variants displayed for comparison" src="https://github.com/user-attachments/assets/c39b1305-ad14-4958-be39-d714d53d1990" />
115
133
 
116
134
  ## License
117
135
 
118
- MIT License. See [MIT-LICENSE](MIT-LICENSE).
136
+ [MIT License](MIT-LICENSE).
@@ -2,11 +2,11 @@
2
2
  <div style="position: fixed; bottom: 1rem; right: 1rem; background: #18181b; color: white; padding: 0.75rem; border-radius: 0.5rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); z-index: 9999; font-size: 0.875rem; font-family: system-ui, -apple-system, sans-serif;">
3
3
  <span style="color: #a1a1aa; margin-right: 0.5rem;">View:</span>
4
4
  <%= link_to "Default",
5
- url_for(request.params.except(ActionVersionPreview.param_name.to_s, ActionVersionPreview.param_name.to_sym)),
5
+ variant_preview_path,
6
6
  style: "padding: 0.25rem 0.5rem; border-radius: 0.25rem; text-decoration: none; color: white; #{current_variant.nil? ? 'background: #2563eb;' : ''}" %>
7
7
  <% detected_variants.each do |variant| %>
8
8
  <%= link_to variant.upcase,
9
- url_for(request.params.merge(ActionVersionPreview.param_name => variant)),
9
+ variant_preview_path(variant),
10
10
  style: "padding: 0.25rem 0.5rem; border-radius: 0.25rem; text-decoration: none; color: white; #{current_variant.to_s == variant ? 'background: #2563eb;' : ''}" %>
11
11
  <% end %>
12
12
  </div>
@@ -0,0 +1,46 @@
1
+ # ActionVersionPreview 0.2.0
2
+
3
+ ActionVersionPreview 0.2.0 fixes deployment-time helper loading and makes the
4
+ built-in switcher's links safe to use with arbitrary query filters.
5
+
6
+ ## Fixed
7
+
8
+ - Fixed `NameError: uninitialized constant ActionVersionPreview::SwitcherHelper`
9
+ when Rails loads controllers or views early during application initialization
10
+ and asset precompilation. The helper is loaded explicitly and remains available
11
+ across development reloads.
12
+ - Switcher links now use the current request path and query data. Parameters such
13
+ as `host`, `protocol`, and `script_name` cannot redirect links to another origin
14
+ or path. Nested filters, pagination, mounted prefixes, and custom preview
15
+ parameter names are preserved.
16
+ - POST body fields, including submitted form fields, are no longer copied into
17
+ switcher URLs.
18
+
19
+ ## Documentation and validation
20
+
21
+ - Added a [complete usage guide](https://github.com/aviflombaum/action_version_preview/blob/main/docs/usage.md) covering installation, permissions,
22
+ configuration, discovery, navigation, integrations, and troubleshooting.
23
+ - Added regression tests for early/lazy loading, reloading, installed-gem
24
+ production asset precompilation, and switcher URLs.
25
+ - Refreshed development dependency resolution and CI actions.
26
+
27
+ ## Upgrade
28
+
29
+ ```ruby
30
+ gem "action_version_preview", "~> 0.2.0"
31
+ ```
32
+
33
+ Run `bundle update action_version_preview` and restart or rebuild your application.
34
+ There are no migrations or required configuration changes. The Default link still
35
+ removes the preview parameter and closes the widget.
36
+
37
+ ## Compatibility and scope
38
+
39
+ Declared requirements remain Ruby >= 3.1 and Rails >= 7.0, < 9.0; your Rails version
40
+ may require a newer Ruby. Local release checks ran with Ruby 4.0.5 / Rails 8.1.3.1.
41
+ A full supported-version matrix is not yet in place.
42
+
43
+ This release does not add variant persistence or layout/partial/component
44
+ scanning. Preview parameters must be scalar strings; malformed array/hash input
45
+ handling remains a known limitation. See the [usage guide](https://github.com/aviflombaum/action_version_preview/blob/main/docs/usage.md) for the
46
+ current discovery and access-check behavior.
data/docs/releasing.md ADDED
@@ -0,0 +1,97 @@
1
+ # Releasing ActionVersionPreview
2
+
3
+ Use this procedure to publish a tested gem and then attach a GitHub tag/release
4
+ to the exact source revision used to build it. RubyGems publication is performed
5
+ manually by the maintainer.
6
+
7
+ ## Prepare and validate
8
+
9
+ 1. Audit the local and remote release branches. Merge all intended changes into
10
+ the release branch, including any new commits from remote `main`.
11
+ 2. Update `lib/action_version_preview/version.rb`, the local gem entry in
12
+ `Gemfile.lock`, the changelog, README, usage guide, and release notes.
13
+ 3. Run the checks below. Security tools are maintainer tools, not runtime gem
14
+ dependencies. Use current installed versions and an updated advisory database.
15
+
16
+ ```sh
17
+ bundle check
18
+ bin/rails test
19
+ CI=true bin/rails test
20
+ bin/rubocop
21
+ RAILS_ENV=test bundle exec rake app:zeitwerk:check
22
+ bundle-audit check --update
23
+ brakeman --force-scan --no-pager .
24
+ brakeman --no-pager --add-engines-path ../.. --gemfile ../../Gemfile test/dummy
25
+ erb_lint --lint-all --enable-linters erb_safety,parser_errors
26
+ npx --yes markdownlint-cli2@0.23.2 README.md CHANGELOG.md SUGGESTIONS.md docs/usage.md docs/releasing.md docs/releases/0.2.0.md
27
+ git diff --check
28
+ ```
29
+
30
+ The test suite builds and installs the gem into a temporary directory, checks
31
+ early loading/reloading, renders the real helper, and compiles a production asset
32
+ with `SECRET_KEY_BASE_DUMMY=1`. Brakeman needs the relative `../../Gemfile` argument
33
+ to identify the dummy app's shared dependency resolution correctly.
34
+
35
+ Check Markdown and all local/public documentation links as part of release
36
+ preparation. Avoid treating a remote server's rate limit as a broken link without
37
+ checking it separately.
38
+
39
+ ## Merge and build
40
+
41
+ After validation, commit the release changes, merge the release branch into
42
+ `main`, and push `main`. Wait for GitHub CI to pass before publication. Verify that
43
+ each intended release-branch tip is an ancestor of `main`.
44
+
45
+ From a clean `main` checkout:
46
+
47
+ ```sh
48
+ git rev-parse HEAD
49
+ bundle exec rake build
50
+ shasum -a 256 pkg/action_version_preview-0.2.0.gem
51
+ ```
52
+
53
+ Record that commit SHA and the gem's checksum. Inspect the archive for the runtime
54
+ files, helper partial, license, README, changelog, and public documentation. It
55
+ must not contain development credentials, generated assets, tests, or research
56
+ notes. Check that packaged files are readable by other users.
57
+
58
+ ## Maintainer publishes to RubyGems
59
+
60
+ Use the already-built artifact:
61
+
62
+ ```sh
63
+ gem push pkg/action_version_preview-0.2.0.gem
64
+ ```
65
+
66
+ Complete RubyGems authentication/MFA as prompted. See the official
67
+ [RubyGems publishing guide](https://guides.rubygems.org/publishing/) for account
68
+ setup. Do not rebuild or change the release commit after this artifact is pushed.
69
+
70
+ ## Verify publication, then tag and release on GitHub
71
+
72
+ Confirm RubyGems lists version 0.2.0 and compare its SHA-256 with the local artifact.
73
+ The version API provides the published checksum:
74
+
75
+ ```text
76
+ https://rubygems.org/api/v2/rubygems/action_version_preview/versions/0.2.0.json
77
+ ```
78
+
79
+ Then use the recorded release commit, rather than assuming `main` has not moved:
80
+
81
+ ```sh
82
+ git tag -a v0.2.0 RELEASE_COMMIT_SHA -m "ActionVersionPreview 0.2.0"
83
+ git push origin v0.2.0
84
+ gh release create v0.2.0 pkg/action_version_preview-0.2.0.gem \
85
+ --verify-tag \
86
+ --repo aviflombaum/action_version_preview \
87
+ --title "ActionVersionPreview 0.2.0" \
88
+ --notes-file docs/releases/0.2.0.md
89
+ ```
90
+
91
+ Replace `RELEASE_COMMIT_SHA` with the recorded SHA. Inspect the resulting GitHub
92
+ release and verify its tag targets that commit. If a tag or release already
93
+ exists, inspect it before proceeding; do not move a published tag.
94
+
95
+ `bundle exec rake release` combines tagging, Git pushes, and RubyGems publication.
96
+ Do not use it for this staged workflow: the manual RubyGems push must happen before
97
+ the GitHub tag and release.
data/docs/usage.md ADDED
@@ -0,0 +1,295 @@
1
+ # ActionVersionPreview usage guide
2
+
3
+ This guide describes the public behavior of ActionVersionPreview 0.2.0. The gem
4
+ lets you compare Rails UI designs by selecting a view variant for an individual
5
+ request. It does not assign visitors to experiments or store a selected design in
6
+ a session.
7
+
8
+ ## Installation
9
+
10
+ Add the gem to an application's Gemfile:
11
+
12
+ ```ruby
13
+ gem "action_version_preview", "~> 0.2.0"
14
+ ```
15
+
16
+ Run `bundle install` and restart Rails. You do not need to mount routes, run
17
+ migrations, generate configuration, or install JavaScript. Controllers inheriting
18
+ from `ActionController::Base` receive the preview behavior automatically. The
19
+ switcher is optional; variant URLs work without it.
20
+
21
+ The gem declares Ruby >= 3.1 and Rails >= 7.0, < 9.0. Choose a Ruby version supported
22
+ by your Rails release. API-only controllers inheriting directly from
23
+ `ActionController::API` are outside the automatic integration.
24
+
25
+ ## Create your first preview
26
+
27
+ Suppose your application already renders `DashboardController#show` at
28
+ `/dashboard`. Keep the default view and add a variant:
29
+
30
+ ```text
31
+ app/views/dashboard/show.html.erb
32
+ app/views/dashboard/show.html+v2.erb
33
+ ```
34
+
35
+ For example, the variant might contain:
36
+
37
+ ```erb
38
+ <h1>A new dashboard design</h1>
39
+ <%= render "summary" %>
40
+ ```
41
+
42
+ Visit `/dashboard?vv=v2` in development. Open `/dashboard` in another tab to compare
43
+ the original. Both requests use your normal controller action, authentication,
44
+ and data; only template selection changes.
45
+
46
+ Add more files such as `show.html+redesign.erb` as needed. The name after `+` is
47
+ case-sensitive. Lowercase letters, digits, and underscores work with the built-in
48
+ discovery, for example `v2`, `redesign`, and `new_layout`.
49
+
50
+ Rails performs the actual [variant template selection](https://guides.rubyonrails.org/layouts_and_rendering.html#the-variants-option).
51
+ If an action has no matching variant template but does have a normal template,
52
+ Rails can fall back to that normal template. Selecting a name does not prove a
53
+ matching template was rendered.
54
+
55
+ ## URL behavior
56
+
57
+ These examples use the default parameter name, `vv`, and assume access is allowed:
58
+
59
+ | Request | Behavior |
60
+ | --- | --- |
61
+ | `/dashboard` | Leaves normal application variant selection unchanged |
62
+ | `/dashboard?vv=v2` | Sets the request variant to `v2` |
63
+ | `/dashboard?vv=true` | Enables preview mode without changing the request variant |
64
+ | `/dashboard?vv=` | Does not enable preview mode or change the request variant |
65
+ | `/dashboard?vv=unknown` | Requests `unknown`; Rails may render the default template |
66
+
67
+ `true` is a reserved trigger string. It cannot be used to select a template variant
68
+ named `true` through this parameter. `false` is an ordinary variant name, not a
69
+ switch to disable previews. Remove the parameter to leave preview mode.
70
+
71
+ Use scalar strings. In 0.2.0, array/hash forms such as `vv[]=v2` or `vv[name]=v2`
72
+ are unsupported and may raise an exception when access is allowed. Malformed-input
73
+ handling is a separate planned improvement.
74
+
75
+ When access is denied, the gem leaves the request variant unchanged and hides the
76
+ switcher. It does not render an access-denied page or change the response status.
77
+
78
+ ## Add the switcher
79
+
80
+ Place this inside an HTML layout, usually near the closing `</body>` tag:
81
+
82
+ ```erb
83
+ <%= variant_switcher %>
84
+ ```
85
+
86
+ The widget appears when all three conditions hold:
87
+
88
+ 1. The configured preview parameter has a nonblank value.
89
+ 2. The access check permits the request.
90
+ 3. The current action has discoverable variant templates.
91
+
92
+ It works when `config.action_controller.include_all_helpers = false`. It uses
93
+ ordinary links and inline styles, with no JavaScript dependency.
94
+
95
+ ### Switching and returning to Default
96
+
97
+ A variant link selects that name on the current path. Other query data, including
98
+ nested filters and pagination, is preserved. For example:
99
+
100
+ ```text
101
+ /reports?page=2&vv=v2
102
+ /reports?page=2&vv=redesign
103
+ ```
104
+
105
+ Mounted application prefixes are preserved. Query keys that happen to be named
106
+ `host`, `protocol`, or `script_name` remain query data and cannot override the link
107
+ destination. Request-body fields are not copied into the links.
108
+
109
+ **Default** removes the preview parameter. The resulting page follows your normal
110
+ application rendering and the switcher disappears. Use `?vv=true` to show it again.
111
+ There is no separate persistent preview session or exit button in 0.2.0.
112
+
113
+ The highlighted link reflects `current_variant`, the request's first variant.
114
+ If the requested variant falls back to the normal template, the highlight is not
115
+ a report of the template Rails ultimately rendered.
116
+
117
+ ### Styling
118
+
119
+ The supplied widget is fixed at the bottom-right of the viewport and uses inline
120
+ styles. A Content Security Policy that disallows style attributes can prevent
121
+ those styles from applying. There are no theme or position configuration options
122
+ in 0.2.0. You can override the engine partial in your host application at:
123
+
124
+ ```text
125
+ app/views/action_version_preview/_variant_switcher.html.erb
126
+ ```
127
+
128
+ Keep application-specific overrides under review when upgrading. Custom styles
129
+ and markup are your application's responsibility.
130
+
131
+ ## Configuration
132
+
133
+ Configure the gem once at application startup:
134
+
135
+ ```ruby
136
+ # config/initializers/action_version_preview.rb
137
+ ActionVersionPreview.configure do |config|
138
+ config.param_name = :design
139
+ end
140
+ ```
141
+
142
+ You can then use `/dashboard?design=v2` or `/dashboard?design=true`. The switcher
143
+ uses the configured name for every link. Strings and symbols are both accepted.
144
+ Choose a name that does not conflict with your application's existing parameters.
145
+ Configuration is application-wide, not per-user or per-request.
146
+
147
+ ### Access control and callback order
148
+
149
+ The default check allows only `Rails.env.development?` or `Rails.env.test?`.
150
+ Production, staging, and custom environments are disabled by default. To allow
151
+ administrators as well:
152
+
153
+ ```ruby
154
+ ActionVersionPreview.configure do |config|
155
+ config.access_check = ->(controller) {
156
+ Rails.env.development? || Rails.env.test? ||
157
+ (controller.respond_to?(:current_user, true) &&
158
+ controller.send(:current_user)&.admin?)
159
+ }
160
+ end
161
+ ```
162
+
163
+ The callable receives the current controller. A truthy return value allows
164
+ previewing; `false` or `nil` denies it. The example supports a private
165
+ `current_user` method, but you must adapt the method and permission check to your
166
+ application. Avoid performing side effects: the check can run during variant
167
+ selection and again while rendering helpers.
168
+
169
+ The gem registers its callback on `ActionController::Base`. A user lookup that
170
+ happens only in a later host `before_action` may not be ready when selection runs.
171
+ Prefer a current-user lookup that can resolve its own state when called. If your
172
+ authentication requires a callback first, order that callback before preview
173
+ selection using Rails' callback ordering facilities, and test the request in your
174
+ host application. The gem has no callback-order configuration setting in 0.2.0.
175
+
176
+ Keep your normal authorization checks for data and actions. The preview access
177
+ check governs selection through this gem; it is not a replacement for host
178
+ application authorization. Excluding a name from the widget is not an access rule.
179
+
180
+ ## Template discovery
181
+
182
+ The built-in switcher scans:
183
+
184
+ ```text
185
+ app/views/<controller_path>/<action_name>.html+<variant>.erb
186
+ ```
187
+
188
+ For example, `Admin::ReportsController#index` is scanned under
189
+ `app/views/admin/reports/index.html+*.erb`. Names are returned in sorted order and
190
+ cached for that request.
191
+
192
+ The following names are omitted from the widget: `mobile`, `tablet`, `phone`, and
193
+ `desktop`. These are the gem's device-name convention, not names reserved by Rails.
194
+ An authorized `?vv=mobile` request can still select a mobile template.
195
+
196
+ In 0.2.0, automatic discovery does not enumerate:
197
+
198
+ - Layout-only or partial-only variants.
199
+ - Templates in engine-owned or custom/prepended view directories.
200
+ - Locale-qualified filenames such as `show.en.html+v2.erb`.
201
+ - Handlers other than ERB, such as Haml or Slim.
202
+ - Variant names containing hyphens or other characters outside letters, digits,
203
+ and underscores.
204
+ - A different template explicitly rendered by the action.
205
+
206
+ Rails may still render these variants when selected directly by URL. Discovery
207
+ controls the widget's choices; it is not an allowlist for request variants.
208
+
209
+ ## Layouts, partials, and components
210
+
211
+ Native Rails rendering can apply the selected variant to layouts and partials.
212
+ For example, a request selecting `v2` can use these files where your normal render
213
+ calls refer to them:
214
+
215
+ ```text
216
+ app/views/layouts/application.html+v2.erb
217
+ app/views/dashboard/_summary.html+v2.erb
218
+ ```
219
+
220
+ The switcher does not discover a `v2` choice from those files alone. Use the URL
221
+ directly, or also provide an action variant when you want automatic discovery.
222
+
223
+ ViewComponent and other rendering libraries have their own variant behavior.
224
+ ActionVersionPreview sets `request.variant`; it does not scan component directories
225
+ or provide a dedicated component integration. Test component rendering in your
226
+ host application.
227
+
228
+ Mailers and background jobs do not automatically inherit the preview URL or its
229
+ variant. Pass and select the desired variant in those systems separately.
230
+
231
+ ## Navigation and existing application variants
232
+
233
+ Selection lasts for the current request. Normal links, redirects, form submissions,
234
+ and Turbo requests only keep preview mode if their destination includes the
235
+ configured parameter. The gem does not modify `default_url_options`, cookies,
236
+ sessions, localStorage, or Turbo behavior.
237
+
238
+ For a link where you explicitly want to carry the current variant forward, add it
239
+ with your application's route helper:
240
+
241
+ ```erb
242
+ <%= link_to "Reports", reports_path(design: current_variant) %>
243
+ ```
244
+
245
+ This example assumes `config.param_name = :design`. If you want to open the
246
+ switcher without selecting a variant, pass `design: "true"` instead. Consider
247
+ whether the destination action actually has the same variants.
248
+
249
+ A nonblank preview name replaces the request's variant selection. Missing/blank
250
+ parameters, denied access, and the `true` trigger leave an existing application
251
+ variant unchanged. Other controller callbacks can still change it later.
252
+
253
+ ## Helper reference
254
+
255
+ | Method | Available in | Meaning |
256
+ | --- | --- | --- |
257
+ | `variant_switcher` | Views | Renders the built-in widget, or no visible widget when its conditions are not met |
258
+ | `current_variant` | Controller and views | First request variant as a symbol, or `nil` |
259
+ | `detected_variants` | Controller and views | Sorted array of discovered names as strings |
260
+ | `variant_preview_active?` | Controller and views | Truthy when the parameter is nonblank and access is allowed |
261
+ | `can_preview_variants?` | Controller and views | Result of the configured access check |
262
+
263
+ The controller methods are private and exposed as view helpers. Use the widget
264
+ or normal Rails route helpers for your own links.
265
+
266
+ ## Troubleshooting
267
+
268
+ | Symptom | What to check |
269
+ | --- | --- |
270
+ | No switcher | Include the helper, provide `vv=true`, allow access, and check that action ERB variants are discoverable |
271
+ | Works locally but not in staging | Configure `access_check`; staging is denied by default |
272
+ | Requested variant renders the original view | Check spelling, case, filename, access, callback order, and Rails fallback behavior |
273
+ | Default makes the switcher disappear | Expected in 0.2.0; Default removes the parameter |
274
+ | Variant disappears after navigation | Include the preview parameter in that destination if you want to retain it |
275
+ | Device variant changes unexpectedly | A concrete preview name replaces the request variant; check other callbacks too |
276
+ | Widget is unstyled | Check the host's Content Security Policy and any partial override |
277
+ | 0.1.0 raises `SwitcherHelper` during asset precompilation | Upgrade to 0.2.0, update the lockfile, and rebuild the deployment image |
278
+
279
+ ## Upgrading from 0.1.0
280
+
281
+ Update your version constraint if it pins 0.1.0, then run:
282
+
283
+ ```sh
284
+ bundle update action_version_preview
285
+ ```
286
+
287
+ Restart Rails or rebuild your deployment image so it installs the new lockfile
288
+ resolution. No migration, route mount, or new initializer is required.
289
+
290
+ 0.2.0 fixes helper loading during boot and asset precompilation, and changes the
291
+ built-in switcher to derive URLs from the current path and query data. POST body
292
+ fields no longer appear in its links. The configuration names, default access
293
+ policy, discovery scope, and Default behavior remain the same.
294
+
295
+ See the [changelog](../CHANGELOG.md) and [release notes](releases/0.2.0.md).
@@ -1,4 +1,5 @@
1
1
  require "action_version_preview/controller_methods"
2
+ require "action_version_preview/switcher_helper"
2
3
 
3
4
  module ActionVersionPreview
4
5
  class Engine < ::Rails::Engine
@@ -7,7 +8,12 @@ module ActionVersionPreview
7
8
  initializer "action_version_preview.controller_methods" do
8
9
  ActiveSupport.on_load(:action_controller_base) do
9
10
  include ActionVersionPreview::ControllerMethods
10
- helper ActionVersionPreview::Engine.helpers
11
+ end
12
+ end
13
+
14
+ initializer "action_version_preview.helpers", after: :load_config_initializers do
15
+ ActiveSupport.on_load(:action_view) do
16
+ include ActionVersionPreview::SwitcherHelper
11
17
  end
12
18
  end
13
19
  end
@@ -0,0 +1,18 @@
1
+ module ActionVersionPreview
2
+ # Required by the engine: load hooks may run before Rails sets up autoloading.
3
+ module SwitcherHelper
4
+ def variant_switcher
5
+ render partial: "action_version_preview/variant_switcher"
6
+ end
7
+
8
+ private
9
+
10
+ def variant_preview_path(variant = nil)
11
+ param_name = ActionVersionPreview.param_name.to_s
12
+ query = request.query_parameters.except(param_name)
13
+ query[param_name] = variant if variant
14
+
15
+ query.empty? ? request.path : "#{request.path}?#{query.to_query}"
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module ActionVersionPreview
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_version_preview
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Avi Flombaum
@@ -38,24 +38,29 @@ executables: []
38
38
  extensions: []
39
39
  extra_rdoc_files: []
40
40
  files:
41
+ - CHANGELOG.md
41
42
  - MIT-LICENSE
42
43
  - README.md
43
44
  - Rakefile
44
- - app/helpers/action_version_preview/switcher_helper.rb
45
45
  - app/views/action_version_preview/_variant_switcher.html.erb
46
46
  - config/routes.rb
47
+ - docs/releases/0.2.0.md
48
+ - docs/releasing.md
49
+ - docs/usage.md
47
50
  - lib/action_version_preview.rb
48
51
  - lib/action_version_preview/controller_methods.rb
49
52
  - lib/action_version_preview/engine.rb
53
+ - lib/action_version_preview/switcher_helper.rb
50
54
  - lib/action_version_preview/version.rb
51
55
  - lib/tasks/action_version_preview_tasks.rake
52
56
  homepage: https://github.com/aviflombaum/action_version_preview
53
57
  licenses:
54
58
  - MIT
55
59
  metadata:
56
- homepage_uri: https://github.com/aviflombaum/action_version_preview
57
60
  source_code_uri: https://github.com/aviflombaum/action_version_preview
58
61
  changelog_uri: https://github.com/aviflombaum/action_version_preview/blob/main/CHANGELOG.md
62
+ documentation_uri: https://github.com/aviflombaum/action_version_preview/blob/main/docs/usage.md
63
+ bug_tracker_uri: https://github.com/aviflombaum/action_version_preview/issues
59
64
  rdoc_options: []
60
65
  require_paths:
61
66
  - lib
@@ -70,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
75
  - !ruby/object:Gem::Version
71
76
  version: '0'
72
77
  requirements: []
73
- rubygems_version: 3.6.9
78
+ rubygems_version: 4.0.14
74
79
  specification_version: 4
75
80
  summary: Preview multiple view variants side-by-side using Rails' built-in view variants.
76
81
  test_files: []
@@ -1,7 +0,0 @@
1
- module ActionVersionPreview
2
- module SwitcherHelper
3
- def variant_switcher
4
- render partial: "action_version_preview/variant_switcher"
5
- end
6
- end
7
- end