action_version_preview 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e6d263561ee95f415d87df090bb1e0008500048a1a896c96ae2df8ba7690e6b7
4
+ data.tar.gz: fc72870345f275e9e40fe4d75a5bba496467239e577fb524ecfd2e4b9b740fb2
5
+ SHA512:
6
+ metadata.gz: f8353f9edc0f76a49425d6a5147b814fc6ca82304c465c186f33a94a8c42f61cea4d8120c0f8c5332636f277705e43e3755f1d38b10706137902d356d5c98011
7
+ data.tar.gz: 87099d11c6a79710263dc7c5b8f58a0e91a066279f60a9f4ad8b2c2a011096f1e33b79db414719b61a237e4f755b0c3950dedc77dd48272135b5138a03e7af80
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright TODO: Write your name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # ActionVersionPreview
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.
4
+
5
+ ## Why?
6
+
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.
8
+
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:
16
+
17
+ ```ruby
18
+ gem "action_version_preview"
19
+ ```
20
+
21
+ Run `bundle install`. That's it. The concern is automatically included in all controllers.
22
+
23
+ ## Usage
24
+
25
+ ### 1. Create Variant Templates
26
+
27
+ Rails looks for variant templates using this naming convention:
28
+
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
33
+ ```
34
+
35
+ This works for layouts, partials, mailers, and ViewComponent templates too.
36
+
37
+ ### 2. Visit the Variant URL
38
+
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
+ ```
44
+
45
+ ### 3. Add the Variant Switcher (Optional)
46
+
47
+ Drop the built-in switcher widget in your layout:
48
+
49
+ ```erb
50
+ <%= variant_switcher %>
51
+ ```
52
+
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)
57
+
58
+ Standard Rails variants (`mobile`, `tablet`, `phone`, `desktop`) are excluded from detection.
59
+
60
+ ## Configuration
61
+
62
+ Zero config is the default. But if you need to customize:
63
+
64
+ ```ruby
65
+ # config/initializers/action_version_preview.rb
66
+ 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:
72
+ config.access_check = ->(controller) {
73
+ Rails.env.development? ||
74
+ Rails.env.test? ||
75
+ controller.current_user&.admin?
76
+ }
77
+ end
78
+ ```
79
+
80
+ ## Helper Methods
81
+
82
+ These are available in controllers and views:
83
+
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 |
91
+
92
+ ## How It Works
93
+
94
+ Under the hood, ActionVersionPreview sets `request.variant` based on the URL parameter. Rails' template resolver then automatically looks for matching variant templates.
95
+
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
101
+
102
+ ## Comparison: Feature Flags vs View Variants
103
+
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 |
110
+
111
+ ## Requirements
112
+
113
+ - Rails 7.0+ or 8.x
114
+ - Ruby 3.1+
115
+
116
+ ## License
117
+
118
+ MIT License. See [MIT-LICENSE](MIT-LICENSE).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ module ActionVersionPreview
2
+ module SwitcherHelper
3
+ def variant_switcher
4
+ render partial: "action_version_preview/variant_switcher"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ <% if variant_preview_active? && detected_variants.any? %>
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
+ <span style="color: #a1a1aa; margin-right: 0.5rem;">View:</span>
4
+ <%= link_to "Default",
5
+ url_for(request.params.except(ActionVersionPreview.param_name.to_s, ActionVersionPreview.param_name.to_sym)),
6
+ style: "padding: 0.25rem 0.5rem; border-radius: 0.25rem; text-decoration: none; color: white; #{current_variant.nil? ? 'background: #2563eb;' : ''}" %>
7
+ <% detected_variants.each do |variant| %>
8
+ <%= link_to variant.upcase,
9
+ url_for(request.params.merge(ActionVersionPreview.param_name => variant)),
10
+ style: "padding: 0.25rem 0.5rem; border-radius: 0.25rem; text-decoration: none; color: white; #{current_variant.to_s == variant ? 'background: #2563eb;' : ''}" %>
11
+ <% end %>
12
+ </div>
13
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,51 @@
1
+ module ActionVersionPreview
2
+ module ControllerMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ # Standard Rails variants that should be excluded from detection
6
+ STANDARD_VARIANTS = %w[mobile tablet phone desktop].freeze
7
+
8
+ included do
9
+ before_action :set_view_variant
10
+ helper_method :current_variant, :detected_variants, :can_preview_variants?, :variant_preview_active?
11
+ end
12
+
13
+ private
14
+
15
+ def set_view_variant
16
+ variant = params[ActionVersionPreview.param_name]
17
+ return unless variant.present?
18
+ return unless can_preview_variants?
19
+
20
+ # Set the variant if it's not just a truthy trigger value
21
+ request.variant = variant.to_sym unless variant.to_s == "true"
22
+ end
23
+
24
+ def current_variant
25
+ request.variant&.first
26
+ end
27
+
28
+ # Returns true if the variant preview mode is active (param is present)
29
+ def variant_preview_active?
30
+ params[ActionVersionPreview.param_name].present? && can_preview_variants?
31
+ end
32
+
33
+ # Auto-detect variant files for the current controller action
34
+ def detected_variants
35
+ @detected_variants ||= begin
36
+ view_path = Rails.root.join("app", "views", controller_path)
37
+ pattern = "#{action_name}.html+*.erb"
38
+
39
+ Dir.glob(view_path.join(pattern)).filter_map do |file|
40
+ match = File.basename(file).match(/\.html\+(\w+)\.erb$/)
41
+ variant = match[1] if match
42
+ variant unless STANDARD_VARIANTS.include?(variant)
43
+ end.sort
44
+ end
45
+ end
46
+
47
+ def can_preview_variants?
48
+ ActionVersionPreview.access_check.call(self)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ require "action_version_preview/controller_methods"
2
+
3
+ module ActionVersionPreview
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace ActionVersionPreview
6
+
7
+ initializer "action_version_preview.controller_methods" do
8
+ ActiveSupport.on_load(:action_controller_base) do
9
+ include ActionVersionPreview::ControllerMethods
10
+ helper ActionVersionPreview::Engine.helpers
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module ActionVersionPreview
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ require "action_version_preview/version"
2
+ require "action_version_preview/engine"
3
+
4
+ module ActionVersionPreview
5
+ class << self
6
+ # The URL parameter name used to specify the variant (default: :vv)
7
+ # Example: /dashboard?vv=v2
8
+ attr_accessor :param_name
9
+
10
+ # Proc that determines if the current user can preview variants
11
+ # Default: always true in development/test, always false in production
12
+ # Example: ->(controller) { controller.current_user&.admin? }
13
+ attr_accessor :access_check
14
+ end
15
+
16
+ # Set sensible defaults
17
+ self.param_name = :vv
18
+ self.access_check = ->(controller) {
19
+ Rails.env.development? || Rails.env.test?
20
+ }
21
+
22
+ def self.configure
23
+ yield self
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :action_version_preview do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: action_version_preview
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Avi Flombaum
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
32
+ description: A zero-config Rails engine that leverages Rails' view variants to let
33
+ you preview different versions of your UI simultaneously. Perfect for A/B design
34
+ comparisons, UI iterations, and collecting feedback on redesigns.
35
+ email:
36
+ - im@avi.nyc
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - MIT-LICENSE
42
+ - README.md
43
+ - Rakefile
44
+ - app/helpers/action_version_preview/switcher_helper.rb
45
+ - app/views/action_version_preview/_variant_switcher.html.erb
46
+ - config/routes.rb
47
+ - lib/action_version_preview.rb
48
+ - lib/action_version_preview/controller_methods.rb
49
+ - lib/action_version_preview/engine.rb
50
+ - lib/action_version_preview/version.rb
51
+ - lib/tasks/action_version_preview_tasks.rake
52
+ homepage: https://github.com/aviflombaum/action_version_preview
53
+ licenses:
54
+ - MIT
55
+ metadata:
56
+ homepage_uri: https://github.com/aviflombaum/action_version_preview
57
+ source_code_uri: https://github.com/aviflombaum/action_version_preview
58
+ changelog_uri: https://github.com/aviflombaum/action_version_preview/blob/main/CHANGELOG.md
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 3.1.0
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubygems_version: 3.6.9
74
+ specification_version: 4
75
+ summary: Preview multiple view variants side-by-side using Rails' built-in view variants.
76
+ test_files: []