pin_flags 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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +244 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/javascripts/pin_flags/alpine.min.js +5 -0
  6. data/app/assets/stylesheets/pin_flags/application.css +25 -0
  7. data/app/assets/stylesheets/pin_flags/bulma.min.css +21564 -0
  8. data/app/assets/stylesheets/pin_flags/forms.css +8 -0
  9. data/app/controllers/pin_flags/application_controller.rb +6 -0
  10. data/app/controllers/pin_flags/feature_tags/exports_controller.rb +18 -0
  11. data/app/controllers/pin_flags/feature_tags/feature_subscriptions_controller.rb +138 -0
  12. data/app/controllers/pin_flags/feature_tags/imports_controller.rb +65 -0
  13. data/app/controllers/pin_flags/feature_tags_controller.rb +141 -0
  14. data/app/helpers/pin_flags/application_helper.rb +20 -0
  15. data/app/helpers/pin_flags/feature_tags/feature_subscriptions_helper.rb +23 -0
  16. data/app/helpers/pin_flags/feature_tags/imports_helper.rb +7 -0
  17. data/app/helpers/pin_flags/feature_tags_helper.rb +12 -0
  18. data/app/jobs/pin_flags/application_job.rb +4 -0
  19. data/app/mailers/pin_flags/application_mailer.rb +6 -0
  20. data/app/models/concerns/pin_flags/feature_taggable.rb +39 -0
  21. data/app/models/pin_flags/application_record.rb +5 -0
  22. data/app/models/pin_flags/feature_subscription/bulk_processor.rb +80 -0
  23. data/app/models/pin_flags/feature_subscription.rb +21 -0
  24. data/app/models/pin_flags/feature_tag/cacheable.rb +44 -0
  25. data/app/models/pin_flags/feature_tag.rb +98 -0
  26. data/app/models/pin_flags/page.rb +48 -0
  27. data/app/views/layouts/pin_flags/_flash.html.erb +6 -0
  28. data/app/views/layouts/pin_flags/_modal.html.erb +41 -0
  29. data/app/views/layouts/pin_flags/application.html.erb +34 -0
  30. data/app/views/pin_flags/feature_tags/_empty_feature_tags_table.html.erb +7 -0
  31. data/app/views/pin_flags/feature_tags/_feature_tags_table.html.erb +82 -0
  32. data/app/views/pin_flags/feature_tags/_feature_tags_table_row.html.erb +50 -0
  33. data/app/views/pin_flags/feature_tags/_new_feature_subscription_link.html.erb +8 -0
  34. data/app/views/pin_flags/feature_tags/_new_feature_tag_link.html.erb +6 -0
  35. data/app/views/pin_flags/feature_tags/create.turbo_stream.erb +7 -0
  36. data/app/views/pin_flags/feature_tags/destroy.turbo_stream.erb +7 -0
  37. data/app/views/pin_flags/feature_tags/feature_subscriptions/_empty_feature_subscriptions_table.html.erb +7 -0
  38. data/app/views/pin_flags/feature_tags/feature_subscriptions/_feature_subscriptions_table.html.erb +83 -0
  39. data/app/views/pin_flags/feature_tags/feature_subscriptions/_feature_subscriptions_table_row.html.erb +37 -0
  40. data/app/views/pin_flags/feature_tags/feature_subscriptions/create.turbo_stream.erb +10 -0
  41. data/app/views/pin_flags/feature_tags/feature_subscriptions/destroy.turbo_stream.erb +7 -0
  42. data/app/views/pin_flags/feature_tags/feature_subscriptions/new.html.erb +83 -0
  43. data/app/views/pin_flags/feature_tags/imports/_form.html.erb +66 -0
  44. data/app/views/pin_flags/feature_tags/index.html.erb +123 -0
  45. data/app/views/pin_flags/feature_tags/new.html.erb +34 -0
  46. data/app/views/pin_flags/feature_tags/show.html.erb +89 -0
  47. data/app/views/pin_flags/feature_tags/update.html.erb +2 -0
  48. data/app/views/pin_flags/feature_tags/update.turbo_stream.erb +3 -0
  49. data/app/views/pin_flags/shared/_error_messages.html.erb +11 -0
  50. data/config/routes.rb +12 -0
  51. data/db/migrate/20250626042529_create_feature_tag.rb +12 -0
  52. data/db/migrate/20250626050146_create_feature_subscriptions.rb +15 -0
  53. data/lib/generators/pin_flags/install/install_generator.rb +21 -0
  54. data/lib/generators/pin_flags/install/templates/create_feature_subscriptions.rb +14 -0
  55. data/lib/generators/pin_flags/install/templates/create_feature_tag.rb +12 -0
  56. data/lib/pin_flags/engine.rb +13 -0
  57. data/lib/pin_flags/version.rb +3 -0
  58. data/lib/pin_flags.rb +13 -0
  59. data/lib/tasks/pin_flags_tasks.rake +4 -0
  60. metadata +192 -0
@@ -0,0 +1,66 @@
1
+ <div class="container is-max-desktop">
2
+ <div class="columns is-centered">
3
+ <div class="column is-narrow">
4
+ <%= form_with url: feature_tags_imports_path, data: { turbo: false } do |form| %>
5
+ <div class="field mb-4">
6
+ <div class="is-flex is-align-items-flex-start">
7
+ <%= form.label :json_file, 'FeatureTag JSON File', class: "label" %>
8
+ <span class="has-text-danger ml-1">*</span>
9
+ </div>
10
+ <p class="help is-info mb-2">
11
+ File must be <strong>less than <%= number_to_human_size(PinFlags::FeatureTags::ImportsController::MAX_FILE_SIZE) %></strong>.
12
+ </p>
13
+ <div class="control"
14
+ x-data="{
15
+ fileName: 'No file selected',
16
+ showError: false,
17
+ maxSize: <%= PinFlags::FeatureTags::ImportsController::MAX_FILE_SIZE %>,
18
+ validate(event) {
19
+ const file = event.target.files[0];
20
+
21
+ if (file) {
22
+ this.fileName = file.name;
23
+
24
+ if (file.size > this.maxSize) {
25
+ this.showError = true;
26
+ event.target.classList.add('is-danger');
27
+ event.target.value = '';
28
+ this.fileName = 'No file selected';
29
+ } else {
30
+ this.showError = false;
31
+ event.target.classList.remove('is-danger');
32
+ }
33
+ } else {
34
+ this.fileName = 'No file selected';
35
+ this.showError = false;
36
+ event.target.classList.remove('is-danger');
37
+ }
38
+ }
39
+ }">
40
+ <div class="file has-name is-fullwidth is-boxed">
41
+ <label class="file-label">
42
+ <%= form.file_field :json_file, class: "file-input",
43
+ required: true, accept: ".json",
44
+ "@change": "validate($event)"
45
+ %>
46
+ <span class="file-cta">
47
+ <span class="file-label">Choose a file…</span>
48
+ </span>
49
+ <span class="file-name" x-text="fileName"></span>
50
+ </label>
51
+ </div>
52
+ <p class="help is-danger" x-show="showError">
53
+ File size must be less than <%= number_to_human_size(PinFlags::FeatureTags::ImportsController::MAX_FILE_SIZE) %>.
54
+ </p>
55
+ </div>
56
+ </div>
57
+
58
+ <div class="field is-grouped">
59
+ <div class="control is-expanded">
60
+ <%= form.submit "Upload", class: "button is-primary is-fullwidth" %>
61
+ </div>
62
+ </div>
63
+ <% end %>
64
+ </div>
65
+ </div>
66
+ </div>
@@ -0,0 +1,123 @@
1
+ <% content_for :title, "Feature Tags" %>
2
+
3
+ <nav class="navbar" role="navigation" aria-label="main navigation"
4
+ x-data="{ mobileMenuOpen: false }">
5
+ <div class="navbar-brand">
6
+ <%= link_to pin_flags.root_path, class: "navbar-item" do %>
7
+ <span class="is-size-2 is-size-4-mobile has-text-weight-bold has-text-white">
8
+ ⛳ Pin Flags
9
+ </span>
10
+ <% end %>
11
+
12
+ <a role="button" class="navbar-burger" aria-label="menu"
13
+ :aria-expanded="mobileMenuOpen"
14
+ :class="{ 'is-active': mobileMenuOpen }"
15
+ @click="mobileMenuOpen = !mobileMenuOpen">
16
+ <span aria-hidden="true"></span>
17
+ <span aria-hidden="true"></span>
18
+ <span aria-hidden="true"></span>
19
+ <span aria-hidden="true"></span>
20
+ </a>
21
+ </div>
22
+
23
+ <div class="navbar-menu" :class="{ 'is-active': mobileMenuOpen }">
24
+ <div class="navbar-end">
25
+ <div class="navbar-item">
26
+ <div class="buttons">
27
+ <button class="button is-primary is-dark"
28
+ @click="$dispatch('open-modal', { modalId: '<%= display_import_form_modal_id %>' })">
29
+ Import
30
+ </button>
31
+ <%= link_to "Export", pin_flags.feature_tags_exports_path(format: :json),
32
+ class: "button is-info is-dark" %>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </nav>
38
+
39
+ <div class="mt-4">
40
+ <%= turbo_frame_tag :new_feature_tag do %>
41
+ <%= render 'new_feature_tag_link' %>
42
+ <% end %>
43
+ </div>
44
+
45
+ <div class="mt-5">
46
+ <section x-data="{ timeout: null, delay: 500 }">
47
+ <%= form_with url: pin_flags.feature_tags_path, method: :get,
48
+ data: { turbo_frame: :feature_tags_table } do |form| %>
49
+ <div class="columns">
50
+ <div class="column">
51
+ <%= form.search_field :filter,
52
+ autofocus: true,
53
+ placeholder: "Filter by name...",
54
+ autocomplete: "off",
55
+ value: @filter_param || "",
56
+ maxlength: PinFlags::FeatureTag::MAX_NAME_LENGTH,
57
+ pattern: "[^\s]+.*",
58
+ class: "input is-medium",
59
+ aria: { label: "Search Feature Tags" },
60
+ "x-ref": "filterField",
61
+ "@input": "clearTimeout(timeout); timeout = setTimeout(() => { $el.closest('form').requestSubmit() }, delay)"
62
+ %>
63
+ </div>
64
+ <div class="column">
65
+ <div class="select is-medium mr-3">
66
+ <%= form.select :enabled,
67
+ options_for_select([
68
+ ['Enabled?', ''],
69
+ ['Enabled', 'true'],
70
+ ['Disabled', 'false']
71
+ ], params[:enabled]),
72
+ {},
73
+ {
74
+ aria: { label: "Filter by enabled status" },
75
+ "x-ref": "enabledSelect",
76
+ "@change": "clearTimeout(timeout); timeout = setTimeout(() => { $el.closest('form').requestSubmit() }, delay)"
77
+ }
78
+ %>
79
+ </div>
80
+ <div class="select is-medium">
81
+ <%= form.select :subscriptions,
82
+ options_for_select([
83
+ ['Subscriptions?', ''],
84
+ ['Has Subscriptions', 'true'],
85
+ ['No Subscriptions', 'false']
86
+ ], params[:subscriptions]),
87
+ {},
88
+ {
89
+ aria: { label: "Filter by subscriptions status" },
90
+ "x-ref": "subscriptionsSelect",
91
+ "@change": "clearTimeout(timeout); timeout = setTimeout(() => { $el.closest('form').requestSubmit() }, delay)"
92
+ }
93
+ %>
94
+ </div>
95
+ </div>
96
+ <div class="column is-narrow">
97
+ <button type="button"
98
+ class="button is-light"
99
+ aria-label="Clear all filters"
100
+ @click="
101
+ $refs.filterField.value = '';
102
+ $refs.enabledSelect.value = '';
103
+ $refs.subscriptionsSelect.value = '';
104
+ $el.closest('form').requestSubmit();
105
+ ">
106
+ Clear Filters
107
+ </button>
108
+ </div>
109
+ </div>
110
+ <% end %>
111
+ </section>
112
+
113
+ <% if @feature_tags.any? %>
114
+ <%= turbo_frame_tag :feature_tags_table do %>
115
+ <%= render 'feature_tags_table', feature_tags: @feature_tags, paginator: @paginator,
116
+ filter_param: @filter_param, enabled_param: @enabled_param, subscriptions_param: @subscriptions_param %>
117
+ <% end %>
118
+ <% else %>
119
+ <%= turbo_frame_tag :feature_tags_table do %>
120
+ <%= render 'empty_feature_tags_table', filter_param: @filter_param, enabled_param: @enabled_param, subscriptions_param: @subscriptions_param %>
121
+ <% end %>
122
+ <% end %>
123
+ </div>
@@ -0,0 +1,34 @@
1
+ <%= turbo_frame_tag :new_feature_tag do %>
2
+ <%= form_with model: @feature_tag, url: pin_flags.feature_tags_path do |form| %>
3
+ <%= render "pin_flags/shared/error_messages", resource: @feature_tag %>
4
+
5
+ <div class="field">
6
+ <%= form.label :name, class: "label" %>
7
+ <div class="control">
8
+ <%= form.text_field :name, class: "input",
9
+ placeholder: "Enter feature tag name",
10
+ minlength: PinFlags::FeatureTag::MIN_NAME_LENGTH,
11
+ maxlength: PinFlags::FeatureTag::MAX_NAME_LENGTH,
12
+ autofocus: true, required: true %>
13
+ </div>
14
+ </div>
15
+
16
+ <div class="field">
17
+ <div class="control">
18
+ <label class="checkbox">
19
+ <%= form.check_box :enabled %>
20
+ Enabled?
21
+ </label>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="field is-grouped">
26
+ <div class="control">
27
+ <%= form.submit "Create Feature Tag", class: "button is-success is-outlined" %>
28
+ </div>
29
+ <div class="control">
30
+ <%= link_to "Cancel", pin_flags.feature_tags_path, class: "button is-danger is-outlined" %>
31
+ </div>
32
+ </div>
33
+ <% end %>
34
+ <% end %>
@@ -0,0 +1,89 @@
1
+ <% content_for :title, "Feature Tag #{@feature_tag.name}" %>
2
+
3
+ <nav class="navbar" role="navigation" aria-label="main navigation">
4
+ <div class="navbar-brand">
5
+ <%= link_to pin_flags.root_path, class: "navbar-item" do %>
6
+ <span class="is-size-2 is-size-4-mobile has-text-weight-bold has-text-white">
7
+ ⛳ Pin Flags
8
+ </span>
9
+ <% end %>
10
+ </div>
11
+ </nav>
12
+
13
+ <p class="is-size-4 is-size-5-mobile has-text-weight-bold has-text-white is-clipped">
14
+ <%= @feature_tag.name %>
15
+ </p>
16
+
17
+ <div class="mt-5">
18
+ <%= turbo_frame_tag :new_feature_subscription do %>
19
+ <%= render 'new_feature_subscription_link', feature_tag: @feature_tag %>
20
+ <% end %>
21
+ </div>
22
+
23
+ <div class="container">
24
+ <div class="mt-6 mb-4">
25
+ <span class="has-text-warning has-text-weight-semibold is-size-5">
26
+ Filters
27
+ </span>
28
+ <hr class="has-background-warning" style="height: 2px; width: 50%;">
29
+ </div>
30
+
31
+ <section x-data="{ timeout: null, delay: 500 }">
32
+ <%= form_with url: pin_flags.feature_tag_path(@feature_tag), method: :get,
33
+ data: { turbo_frame: :feature_subscriptions_table } do |form| %>
34
+ <div class="columns">
35
+ <div class="column is-half">
36
+ <div class="field mb-4">
37
+ <%= form.label :feature_taggable_type, 'Taggable Type', class: "label has-text-left" %>
38
+ <div class="control">
39
+ <div class="select is-fullwidth is-medium">
40
+ <%= form.select :feature_taggable_type,
41
+ PinFlags::FeatureTag.feature_taggable_options_for_select,
42
+ {
43
+ prompt: 'All Types',
44
+ selected: @feature_taggable_type,
45
+ include_blank: @feature_taggable_type.present?
46
+ },
47
+ "@change": "clearTimeout(timeout); timeout = setTimeout(() => { $el.closest('form').requestSubmit() }, delay)"
48
+ %>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="field">
54
+ <%= form.label :filter, 'Taggable ID', class: 'label has-text-left' %>
55
+ <div class="control">
56
+ <%= form.search_field :filter, class: "input is-medium",
57
+ placeholder: "Filter feature subscription by ID...",
58
+ autocomplete: "off",
59
+ autofocus: true,
60
+ value: @filter_param || "",
61
+ maxlength: PinFlags::FeatureTag::MAX_NAME_LENGTH,
62
+ pattern: "[^\s]+.*",
63
+ "@input": "clearTimeout(timeout); timeout = setTimeout(() => { $el.closest('form').requestSubmit() }, delay)"
64
+ %>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ <% end %>
70
+ </section>
71
+
72
+ <div class="mt-5">
73
+ <% if @feature_subscriptions.any? %>
74
+ <%= turbo_frame_tag :feature_subscriptions_table do %>
75
+ <%= render "pin_flags/feature_tags/feature_subscriptions/feature_subscriptions_table",
76
+ feature_tag: @feature_tag, feature_subscriptions: @feature_subscriptions, paginator: @paginator,
77
+ filter_param: @filter_param, feature_taggable_type: @feature_taggable_type
78
+ %>
79
+ <% end %>
80
+ <% else %>
81
+ <%= turbo_frame_tag :feature_subscriptions_table do %>
82
+ <%= render 'pin_flags/feature_tags/feature_subscriptions/empty_feature_subscriptions_table',
83
+ filter_param: @filter_param,
84
+ feature_taggable_type: @feature_taggable_type
85
+ %>
86
+ <% end %>
87
+ <% end %>
88
+ </div>
89
+ </div>
@@ -0,0 +1,2 @@
1
+ <h1>FeatureTags#update</h1>
2
+ <p>Find me in app/views/pin_flags/feature_tags/update.html.erb</p>
@@ -0,0 +1,3 @@
1
+ <%= turbo_stream.replace(display_feature_tag_table_turbo_frame_row_id(@feature_tag)) do %>
2
+ <%= render 'feature_tags_table_row', feature_tag: @feature_tag %>
3
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <% if resource.errors.any? %>
2
+ <article id="error_explanation" data-turbo-cache="false" class="message is-danger" role="alert">
3
+ <div class="message-body">
4
+ <ul>
5
+ <% resource.errors.full_messages.each do |message| %>
6
+ <li><%= message %></li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
10
+ </article>
11
+ <% end %>
data/config/routes.rb ADDED
@@ -0,0 +1,12 @@
1
+ PinFlags::Engine.routes.draw do
2
+ namespace :feature_tags do
3
+ resources :imports, only: %i[create]
4
+ resources :exports, only: %i[index]
5
+ end
6
+
7
+ resources :feature_tags, except: %i[edit] do
8
+ resources :feature_subscriptions, only: %i[new create destroy], module: :feature_tags
9
+ end
10
+
11
+ root "feature_tags#index"
12
+ end
@@ -0,0 +1,12 @@
1
+ class CreateFeatureTag < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :pin_flags_feature_tags do |t|
4
+ t.string :name, null: false
5
+ t.boolean :enabled, default: true, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :pin_flags_feature_tags, :name, unique: true
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ class CreateFeatureSubscriptions < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :pin_flags_feature_subscriptions do |t|
4
+ t.references :feature_tag, null: false, foreign_key: { to_table: :pin_flags_feature_tags }
5
+ t.references :feature_taggable, polymorphic: true, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :pin_flags_feature_subscriptions,
11
+ %i[feature_tag_id feature_taggable_type feature_taggable_id],
12
+ unique: true,
13
+ name: 'idx_pf_subs_on_tag_and_taggable'
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module PinFlags
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ include Rails::Generators::Migration
5
+
6
+ source_root File.expand_path("../templates", __FILE__)
7
+
8
+ def copy_migrations
9
+ migration_template "create_feature_tag.rb", "db/migrate/create_feature_tag.rb"
10
+ migration_template "create_feature_subscriptions.rb", "db/migrate/create_feature_subscriptions.rb"
11
+ end
12
+
13
+ def self.next_migration_number(dirname)
14
+ # Increment by 1 second each time to ensure unique timestamps
15
+ @migration_number ||= Time.current.utc.strftime("%Y%m%d%H%M%S").to_i
16
+ @migration_number += 1
17
+ @migration_number.to_s
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ class CreateFeatureSubscriptions < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :pin_flags_feature_subscriptions do |t|
4
+ t.references :feature_tag, null: false, foreign_key: { to_table: :pin_flags_feature_tags }
5
+ t.references :feature_taggable, polymorphic: true, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :pin_flags_feature_subscriptions, [ :feature_taggable_type, :feature_taggable_id ]
11
+ add_index :pin_flags_feature_subscriptions, [ :feature_tag_id, :feature_taggable_type, :feature_taggable_id ],
12
+ unique: true, name: "index_feature_subscriptions_unique"
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ class CreateFeatureTag < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :pin_flags_feature_tags do |t|
4
+ t.string :name, null: false
5
+ t.boolean :enabled, default: true, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :pin_flags_feature_tags, :name, unique: true
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module PinFlags
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace PinFlags
4
+
5
+ initializer "pin_flags.assets.precompile" do |app|
6
+ app.config.assets.precompile += %w[
7
+ pin_flags/alpine.min.js
8
+ pin_flags/bulma.min.css
9
+ pin_flags/application.css
10
+ ]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module PinFlags
2
+ VERSION = "0.1.0"
3
+ end
data/lib/pin_flags.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "pin_flags/version"
2
+ require "pin_flags/engine"
3
+ require "active_support/core_ext/numeric/time"
4
+
5
+ module PinFlags
6
+ mattr_accessor :cache_prefix, default: "pin_flags"
7
+ mattr_accessor :cache_expiry, default: 12.hours
8
+
9
+ def self.config
10
+ yield self if block_given?
11
+ self
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :pin_flags do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pin_flags
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Demetrious Wilson
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: '8.0'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 8.0.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '8.0'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 8.0.2
32
+ - !ruby/object:Gem::Dependency
33
+ name: turbo-rails
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '2.0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '2.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: debug
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.9'
53
+ type: :development
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.9'
60
+ - !ruby/object:Gem::Dependency
61
+ name: rubocop-performance
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.21'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.21'
74
+ - !ruby/object:Gem::Dependency
75
+ name: rubocop-rails-omakase
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.0'
81
+ type: :development
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.0'
88
+ - !ruby/object:Gem::Dependency
89
+ name: propshaft
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '1.0'
95
+ type: :development
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '1.0'
102
+ description: A lightweight Rails engine for managing entity-based feature flags with
103
+ built-in caching support. Pin features to any ActiveRecord model with polymorphic
104
+ associations, providing a flexible alternative to traditional feature flags. Includes
105
+ a configurable cache expiry, and isolated namespace to avoid conflicts.
106
+ email:
107
+ - demetriouswilson@gmail.com
108
+ executables: []
109
+ extensions: []
110
+ extra_rdoc_files: []
111
+ files:
112
+ - MIT-LICENSE
113
+ - README.md
114
+ - Rakefile
115
+ - app/assets/javascripts/pin_flags/alpine.min.js
116
+ - app/assets/stylesheets/pin_flags/application.css
117
+ - app/assets/stylesheets/pin_flags/bulma.min.css
118
+ - app/assets/stylesheets/pin_flags/forms.css
119
+ - app/controllers/pin_flags/application_controller.rb
120
+ - app/controllers/pin_flags/feature_tags/exports_controller.rb
121
+ - app/controllers/pin_flags/feature_tags/feature_subscriptions_controller.rb
122
+ - app/controllers/pin_flags/feature_tags/imports_controller.rb
123
+ - app/controllers/pin_flags/feature_tags_controller.rb
124
+ - app/helpers/pin_flags/application_helper.rb
125
+ - app/helpers/pin_flags/feature_tags/feature_subscriptions_helper.rb
126
+ - app/helpers/pin_flags/feature_tags/imports_helper.rb
127
+ - app/helpers/pin_flags/feature_tags_helper.rb
128
+ - app/jobs/pin_flags/application_job.rb
129
+ - app/mailers/pin_flags/application_mailer.rb
130
+ - app/models/concerns/pin_flags/feature_taggable.rb
131
+ - app/models/pin_flags/application_record.rb
132
+ - app/models/pin_flags/feature_subscription.rb
133
+ - app/models/pin_flags/feature_subscription/bulk_processor.rb
134
+ - app/models/pin_flags/feature_tag.rb
135
+ - app/models/pin_flags/feature_tag/cacheable.rb
136
+ - app/models/pin_flags/page.rb
137
+ - app/views/layouts/pin_flags/_flash.html.erb
138
+ - app/views/layouts/pin_flags/_modal.html.erb
139
+ - app/views/layouts/pin_flags/application.html.erb
140
+ - app/views/pin_flags/feature_tags/_empty_feature_tags_table.html.erb
141
+ - app/views/pin_flags/feature_tags/_feature_tags_table.html.erb
142
+ - app/views/pin_flags/feature_tags/_feature_tags_table_row.html.erb
143
+ - app/views/pin_flags/feature_tags/_new_feature_subscription_link.html.erb
144
+ - app/views/pin_flags/feature_tags/_new_feature_tag_link.html.erb
145
+ - app/views/pin_flags/feature_tags/create.turbo_stream.erb
146
+ - app/views/pin_flags/feature_tags/destroy.turbo_stream.erb
147
+ - app/views/pin_flags/feature_tags/feature_subscriptions/_empty_feature_subscriptions_table.html.erb
148
+ - app/views/pin_flags/feature_tags/feature_subscriptions/_feature_subscriptions_table.html.erb
149
+ - app/views/pin_flags/feature_tags/feature_subscriptions/_feature_subscriptions_table_row.html.erb
150
+ - app/views/pin_flags/feature_tags/feature_subscriptions/create.turbo_stream.erb
151
+ - app/views/pin_flags/feature_tags/feature_subscriptions/destroy.turbo_stream.erb
152
+ - app/views/pin_flags/feature_tags/feature_subscriptions/new.html.erb
153
+ - app/views/pin_flags/feature_tags/imports/_form.html.erb
154
+ - app/views/pin_flags/feature_tags/index.html.erb
155
+ - app/views/pin_flags/feature_tags/new.html.erb
156
+ - app/views/pin_flags/feature_tags/show.html.erb
157
+ - app/views/pin_flags/feature_tags/update.html.erb
158
+ - app/views/pin_flags/feature_tags/update.turbo_stream.erb
159
+ - app/views/pin_flags/shared/_error_messages.html.erb
160
+ - config/routes.rb
161
+ - db/migrate/20250626042529_create_feature_tag.rb
162
+ - db/migrate/20250626050146_create_feature_subscriptions.rb
163
+ - lib/generators/pin_flags/install/install_generator.rb
164
+ - lib/generators/pin_flags/install/templates/create_feature_subscriptions.rb
165
+ - lib/generators/pin_flags/install/templates/create_feature_tag.rb
166
+ - lib/pin_flags.rb
167
+ - lib/pin_flags/engine.rb
168
+ - lib/pin_flags/version.rb
169
+ - lib/tasks/pin_flags_tasks.rake
170
+ homepage: https://github.com/taywils/pin_flags
171
+ licenses:
172
+ - MIT
173
+ metadata:
174
+ source_code_uri: https://github.com/taywils/pin_flags
175
+ rdoc_options: []
176
+ require_paths:
177
+ - lib
178
+ required_ruby_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '3.0'
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ requirements: []
189
+ rubygems_version: 3.6.9
190
+ specification_version: 4
191
+ summary: "⛳PinFlags is a granular, polymorphic feature access system"
192
+ test_files: []