flipside 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c0a3e61bb314f51e06511146b141050b6ca6da1bc7caefa8307f854e19deb78
4
- data.tar.gz: f9465ceb62faf692a5a19addcd5d38251d76712d9ee39b3c634fa9b78be745f1
3
+ metadata.gz: 4893265e134d048226b4a512076453e5fec25f7a842a5a8c8fffd2b8333e5b07
4
+ data.tar.gz: 313e1f9c0269d0418f8bc3a91f36bd92fde267491a08000c774c3597200f5853
5
5
  SHA512:
6
- metadata.gz: f779f75b475ae4b140a65dd6b1da62b9c77f8593000475ee0dec0a5bf7ad4d3170cd1af307d83927312d8c1ee389285d3bf001476a7190c4dff7392cdc7f4f0f
7
- data.tar.gz: 3606e255c03e504d704f147d106783cb920d62c29b8a163b2869bdb50a8f204e4d8c45b0ba524683fe1fc6617b27e23da7b9ad55246df1d12f3e139c1872585d
6
+ metadata.gz: 89efbf2244f81b2662f835129097d04d09193897b74f70939c45ef8063e0172c34c6b161779afc0519d5e0b4cb2bf5c92d27b0a2562bae97053fc86cf5d61805
7
+ data.tar.gz: 399217a46520038a4bb73e2d67fd36e499f445ef74a035a1fefb7a2650c95e5d392de68d182340cd42c5cb64d404c59c64f3d43a9a64d9b6aef56463f5231a44
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.3.0] - 2025-05-15
2
+ - Remove dependency on sinatra
3
+
4
+ ## [0.3.0] - 2025-05-14
5
+ - Rewrite Sinatra UI to Roda
6
+ - Fix links not respecting mount point
7
+ - Add Cache-Control header for js files
8
+
1
9
  ## [0.2.2] - 2025-04-25
2
10
 
3
11
  - Add disabled? method
data/README.md CHANGED
@@ -114,7 +114,7 @@ Flipside.enabled? "MyFeature", user1, user2 # => true, enabled for at least one.
114
114
 
115
115
 
116
116
  ## UI
117
- Flipside comes with a sinatra web ui to mange feature flags. To mount this sinatra app in Rails add the following to your routes.rb file.
117
+ Flipside comes with a Roda web ui to mange feature flags. To mount this roda app in Rails add the following to your routes.rb file.
118
118
  ```ruby
119
119
  mount Flipside::Web, at: '/flipside'
120
120
  ```
@@ -2,49 +2,12 @@ require 'forwardable'
2
2
 
3
3
  class FeaturePresenter
4
4
  extend Forwardable
5
- attr_reader :feature, :base_path
5
+ attr_reader :feature
6
6
 
7
7
  def_delegators :@feature, :name, :description, :enabled, :entities, :roles
8
8
 
9
- def initialize(feature, base_path)
9
+ def initialize(feature)
10
10
  @feature = feature
11
- @base_path = base_path
12
- end
13
-
14
- def href
15
- File.join(base_path, "feature", ERB::Util.url_encode(name))
16
- end
17
-
18
- def toggle_path
19
- File.join(href, "toggle")
20
- end
21
-
22
- def back_path
23
- base_path
24
- end
25
-
26
- def entities_path
27
- File.join(href, "entities")
28
- end
29
-
30
- def add_entity_path
31
- File.join(href, "add_entity")
32
- end
33
-
34
- def remove_entity_path
35
- File.join(href, "remove_entity")
36
- end
37
-
38
- def roles_path
39
- File.join(href, "roles")
40
- end
41
-
42
- def add_role_path
43
- File.join(href, "add_role")
44
- end
45
-
46
- def remove_role_path
47
- File.join(href, "remove_role")
48
11
  end
49
12
 
50
13
  def status
@@ -0,0 +1,26 @@
1
+ module Flipside
2
+ module Importmap
3
+ LIBRARIES = {
4
+ "@hotwired/stimulus": "https://cdn.jsdelivr.net/npm/@hotwired/stimulus/dist/stimulus.js",
5
+ }.freeze
6
+
7
+ def importmap_tags
8
+ JSON.generate({imports:})
9
+ end
10
+
11
+ private
12
+
13
+ def imports
14
+ LIBRARIES.merge(controllers)
15
+ end
16
+
17
+ def controllers
18
+ pattern = File.join(__dir__, "public/*_controller.js")
19
+
20
+ Dir.glob(pattern).to_h do |controller|
21
+ name = File.basename(controller, ".js")
22
+ [name, public_path("#{name}.js")]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,11 +1,9 @@
1
1
  import { Application } from "@hotwired/stimulus"
2
- import ToggleController from "toggle_controller"
3
2
  import SearchController from "search_controller"
4
3
  import ModalController from "modal_controller"
5
4
  import InlineEditController from "inline_edit_controller"
6
5
 
7
6
  const application = Application.start()
8
- application.register("toggle", ToggleController)
9
7
  application.register("search", SearchController)
10
8
  application.register("modal", ModalController)
11
9
  application.register("inline-edit", InlineEditController)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flipside
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -16,7 +16,7 @@
16
16
  </button>
17
17
  </div>
18
18
  <div class="mt-4">
19
- <form action="<%= feature.href %>" method="post">
19
+ <form action="<%= feature_path(feature) %>" method="post">
20
20
  <input type="hidden" name="_method" value="put" />
21
21
  <%# <input type="hidden" name="feature_name" value="<%= feature.name %1>" /> %>
22
22
  <label class="font-bold text-slate-800">Update <%= attribute %></label>
@@ -1,6 +1,10 @@
1
- <div class="p-4 grid grid-cols-12 gap-4 cursor-pointer text-slate-300">
2
- <div class="col-span-3 text-left text-xl"><%= feature.name %></div>
3
- <div class="col-span-7 text-center text-lg"><%= feature.description&.capitalize %></div>
1
+ <div class="p-4 grid grid-cols-12 gap-4 items-center text-slate-300">
2
+ <div class="col-span-10">
3
+ <a href=<%= feature_path(feature) %> class="grid grid-cols-9 cursor-pointer hover:text-slate-400">
4
+ <div class="col-span-3 text-left text-xl"><%= feature.name %></div>
5
+ <div class="col-span-6 text-center text-lg"><%= feature.description&.capitalize %></div>
6
+ </a>
7
+ </div>
4
8
  <div class="col-span-1 text-right text-lg font-normal">
5
9
  <span
6
10
  class="inline-block py-1 px-2 min-w-20 text-center rounded-lg <%= feature.status_color %>"
@@ -9,6 +13,6 @@
9
13
  </span>
10
14
  </div>
11
15
  <div class="text-right">
12
- <%= erb :_toggle_button, locals: {feature:} %>
16
+ <%= render :_toggle_button, locals: {feature:} %>
13
17
  </div>
14
18
  </div>
@@ -1,12 +1,12 @@
1
1
  <% color = feature.enabled ? "bg-blue-700" : "bg-gray-200" %>
2
2
  <% translate = feature.enabled ? "translate-x-5" : "translate-x-0" %>
3
+ <%# FIXME: How to prevent clicking the submit button from propageting? E.g. link navigation on index page. %>
3
4
 
4
- <button type="button"
5
- class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent <%= color %>"
6
- data-controller="toggle"
7
- data-action="click->toggle#switch:prevent"
8
- data-toggle-url-value="<%= feature.toggle_path %>"
9
- data-toggle-enabled-value="<%= feature.enabled %>"
10
- >
11
- <span aria-hidden="true" class="pointer-events-none inline-block size-5 <%= translate %> transform rounded-full bg-white shadow"></span>
12
- </button>
5
+ <form action="<%= feature_path(feature, "toggle") %>" method="post">
6
+ <input type="hidden" name="_method" value="put" />
7
+ <button type="submit"
8
+ class="relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent <%= color %>"
9
+ >
10
+ <span aria-hidden="true" class="pointer-events-none inline-block size-5 <%= translate %> transform rounded-full bg-white shadow"></span>
11
+ </button>
12
+ </form>
@@ -1,10 +1,10 @@
1
1
  <div class="m-12">
2
- <a href="<%= feature.href %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
2
+ <a href="<%= feature_path(feature) %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
3
3
  <div class="mt-12 w-1/3 m-auto bg-gray-800 text-slate-400 p-8 rounded-lg shadow-lg">
4
4
  <h1 class="text-2xl font-bold mb-6">Entities for <span class="font-extrabold text-slate-300"><%= feature.name %></span></h1>
5
5
  <h2 class="text-xl font-bold">Add entity</h2>
6
- <form action="<%= feature.add_entity_path %>" method="post">
7
- <div data-controller="search" data-search-url-value="/flipside/search_entity" class="w-full flex p-0 gap-2 items-center">
6
+ <form action="<%= feature_path(feature, "add_entity") %>" method="post">
7
+ <div data-controller="search" data-search-url-value="<%= search_entity_path %>" class="w-full flex p-0 gap-2 items-center">
8
8
  <select
9
9
  name="class_name"
10
10
  data-search-target="param"
@@ -54,7 +54,7 @@
54
54
  <%= Flipside.display_entity(entity.flippable) %> (<%= entity.flippable_type %>)
55
55
  </div>
56
56
  <div>
57
- <form action="<%= feature.remove_entity_path %>" method="post">
57
+ <form action="<%= feature_path(feature, "remove_entity") %>" method="post">
58
58
  <input type="hidden" name="entity_id" value="<%= entity.id %>" />
59
59
  <button type="submit" class="p-2 bg-gray-300 text-gray-700 font-semibold rounded hover:bg-gray-400">Remove</button>
60
60
  </form>
@@ -1,10 +1,10 @@
1
1
  <div class="m-12">
2
- <a href="<%= feature.href %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
2
+ <a href="<%= feature_path(feature) %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
3
3
  <div class="mt-12 w-1/3 m-auto bg-gray-800 text-slate-400 p-8 rounded-lg shadow-lg">
4
4
  <h1 class="text-2xl font-bold mb-6">Roles for <span class="font-extrabold text-slate-300"><%= feature.name %></span></h1>
5
5
  <h2 class="text-xl font-bold">Add Role</h2>
6
- <form action="<%= feature.add_role_path %>" method="post">
7
- <div data-controller="search" data-search-url-value="/flipside/search_role" class="w-full flex p-0 gap-2 items-center">
6
+ <form action="<%= feature_path(feature, "add_role") %>" method="post">
7
+ <div data-controller="search" data-search-url-value="<%= search_role_path %>" class="w-full flex p-0 gap-2 items-center">
8
8
  <select
9
9
  name="class_name"
10
10
  data-search-target="param"
@@ -54,7 +54,7 @@
54
54
  <%= "#{role.class_name}##{role.method}" %>
55
55
  </div>
56
56
  <div>
57
- <form action="<%= feature.remove_role_path %>" method="post">
57
+ <form action="<%= feature_path(feature, "remove_role") %>" method="post">
58
58
  <input type="hidden" name="role_id" value="<%= role.id %>" />
59
59
  <button type="submit" class="p-2 bg-gray-300 text-gray-700 font-semibold rounded hover:bg-gray-400">Remove</button>
60
60
  </form>
@@ -7,11 +7,13 @@
7
7
 
8
8
  <div class="mt-12 m-auto">
9
9
  <ul>
10
+ <li class="hidden only:block text-center">
11
+ <p>No feature added yet!</p><p>Create a new feature from the console by typing:</p>
12
+ <code class="inline-block p-1 text-green-700 bg-slate-900">Flipside.create(name: "my_feature", description: "Some decription...")</code>
13
+ </li>
10
14
  <% features.each do |feature| %>
11
- <li class="border-b border-slate-500 hover:font-semibold">
12
- <a href=<%= feature.href %>>
13
- <%= erb :_feature_item, locals: {feature:} %>
14
- </a>
15
+ <li class="border-b border-slate-500">
16
+ <%= render :_feature_item, locals: {feature:} %>
15
17
  </li>
16
18
  <% end %>
17
19
  </ul>
@@ -5,15 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <script src="https://cdn.tailwindcss.com"></script>
7
7
  <script type="importmap">
8
- {
9
- "imports": {
10
- "@hotwired/stimulus": "https://cdn.jsdelivr.net/npm/@hotwired/stimulus/dist/stimulus.js",
11
- "toggle_controller": "/flipside/toggle_controller.js",
12
- "search_controller": "/flipside/search_controller.js",
13
- "modal_controller": "/flipside/modal_controller.js",
14
- "inline_edit_controller": "/flipside/inline_edit_controller.js"
15
- }
16
- }
8
+ <%= importmap_tags %>
17
9
  </script>
18
10
  </head>
19
11
  <body class="w-full h-full bg-slate-600">
@@ -21,6 +13,6 @@
21
13
  <%= yield %>
22
14
  </div>
23
15
 
24
- <script src="/flipside/index.js" type="module" ></script>
16
+ <script src="<%= public_path("index.js") %>" type="module" ></script>
25
17
  </body>
26
18
  </html>
@@ -1,5 +1,5 @@
1
1
  <div class="m-12">
2
- <a href="<%= feature.back_path %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
2
+ <a href="<%= base_path %>" class="p-2 rounded-lg text-xl text-slate-300 bg-blue-900 border border-blue-950 hover:bg-blue-800">Back</a>
3
3
  <div class="mt-12 w-1/2 m-auto bg-gray-800 text-slate-400 p-8 rounded-lg shadow-lg">
4
4
  <h1 class="text-2xl font-bold mb-6">Feature Details</h1>
5
5
  <div class="mt-4 grid grid-cols-1 gap-4">
@@ -18,7 +18,7 @@
18
18
  Edit
19
19
  </button>
20
20
  </div>
21
- <form action="<%= feature.href %>" method="post" class="hidden ml-4 grow flex gap-2" data-inline-edit-target="edit">
21
+ <form action="<%= feature_path(feature) %>" method="post" class="hidden ml-4 grow flex gap-2" data-inline-edit-target="edit">
22
22
  <input type="hidden" name="_method" value="put" />
23
23
  <input
24
24
  type="text"
@@ -37,18 +37,18 @@
37
37
  </div>
38
38
  <div class="mt-2 flex justify-between items-center">
39
39
  <span class="font-semibold">Enabled for all:</span>
40
- <span><%= erb :_toggle_button, locals: {feature:} %></span>
40
+ <span><%= render :_toggle_button, locals: {feature:} %></span>
41
41
  </div>
42
42
  <div class="mt-2 flex justify-between items-center">
43
43
  <span class="font-semibold">Active From:</span>
44
44
  <span>
45
- <%= erb :_datetime_modal, locals: {feature:, attribute: :activated_at} %>
45
+ <%= render :_datetime_modal, locals: {feature:, attribute: :activated_at} %>
46
46
  </span>
47
47
  </div>
48
48
  <div class="mt-2 flex justify-between items-center">
49
49
  <span class="font-semibold">Active Until:</span>
50
50
  <span>
51
- <%= erb :_datetime_modal, locals: {feature:, attribute: :deactivated_at} %>
51
+ <%= render :_datetime_modal, locals: {feature:, attribute: :deactivated_at} %>
52
52
  </span>
53
53
  </div>
54
54
  <div class="mt-2 grid grid-cols-3 items-center">
@@ -56,7 +56,7 @@
56
56
  <span class="text-center"><%= feature.entity_count_str %></span>
57
57
  <span class="text-right text-xl text-slate-400">
58
58
  <a
59
- href="<%= feature.entities_path %>"
59
+ href="<%= feature_path(feature, "entities") %>"
60
60
  class="px-2 py-1 rounded-lg bg-blue-900 border border-blue-950 hover:bg-blue-800" >
61
61
  Edit
62
62
  </a>
@@ -67,7 +67,7 @@
67
67
  <span class="text-center"><%= feature.role_count_str %></span>
68
68
  <span class="text-right text-xl text-slate-400">
69
69
  <a
70
- href="<%= feature.roles_path %>"
70
+ href="<%= feature_path(feature, "roles") %>"
71
71
  class="px-2 py-1 rounded-lg bg-blue-900 border border-blue-950 hover:bg-blue-800" >
72
72
  Edit
73
73
  </a>
data/lib/flipside/web.rb CHANGED
@@ -1,105 +1,125 @@
1
- require "sinatra"
1
+ require 'roda'
2
2
  require "uri"
3
3
  require "flipside/feature_presenter"
4
+ require "flipside/importmap"
5
+ require "rack/method_override"
4
6
 
5
7
  module Flipside
6
- class Web < Sinatra::Base
7
- not_found do
8
- erb :not_found
9
- end
10
-
11
- get "/" do
12
- features = Flipside::Feature.order(:name).map do |feature|
13
- FeaturePresenter.new(feature, base_path)
14
- end
15
-
16
- erb :index, locals: {features:}
17
- end
18
-
19
- get "/feature/:name" do
20
- erb :show, locals: {feature: FeaturePresenter.new(feature, base_path)}
8
+ class Web < Roda
9
+ include Flipside::Importmap
10
+ use Rack::MethodOverride
11
+
12
+ opts[:add_script_name] = true
13
+
14
+ plugin :r
15
+ plugin :json
16
+ plugin :halt
17
+ plugin :path
18
+ plugin :all_verbs
19
+ plugin :unescape_path
20
+ plugin :render, views: File.expand_path("views", __dir__)
21
+ plugin :public,
22
+ root: File.expand_path("public", __dir__),
23
+ headers: {"Cache-Control" => "public, max-age=14400"}
24
+
25
+ path(:public) { |name| "/#{name}" }
26
+ path(:base, "/")
27
+ path(:feature) do |feature, *segments, **query|
28
+ path = "/feature/#{ERB::Util.url_encode(feature.name)}"
29
+ path = "#{path}/#{segments.join("/")}" if segments.any?
30
+ path = "#{path}?#{query.map { |k,v| "#{k}=#{v}" }.join("&")}" if query.keys.any?
31
+ path
32
+ end
33
+ path(:search_entity, "/search_entity")
34
+ path(:search_role, "/search_role")
35
+
36
+ def load_feature(name)
37
+ Flipside::Feature.find_by!(name:)
38
+ rescue
39
+ r.halt 404, view(:not_found)
21
40
  end
22
41
 
23
- put "/feature/:name" do
24
- feature.update(params.slice("description"))
42
+ route do |r|
43
+ r.public
25
44
 
26
- redirect to(request.path_info), 303
27
- end
45
+ r.root do
46
+ features = Flipside::Feature.order(:name).map do |feature|
47
+ FeaturePresenter.new(feature)
48
+ end
28
49
 
29
- put "/feature/:name/toggle" do
30
- content_type :text
31
-
32
- if feature.update(enabled: !feature.enabled)
33
- 204
34
- else
35
- [422, "Failed to update feature"]
50
+ view :index, locals: {features:}
36
51
  end
37
- end
38
52
 
39
- put "/feature/:name" do
40
- kwargs = params.slice("activated_at", "deactivated_at")
41
- feature.update(**kwargs)
42
- redirect to("/feature/#{params["name"]}"), 303
43
- end
44
-
45
- get "/feature/:name/entities" do
46
- erb :feature_entities, locals: {feature: FeaturePresenter.new(feature, base_path)}
47
- end
48
-
49
- get '/search_entity' do
50
- class_name = params[:class_name]
51
- query = URI.decode_www_form_component(params[:q])
52
- result = Flipside.search_entity(class_name:, query:)
53
-
54
- erb :_search_result, locals: {result:, class_name:, query:}
55
- end
56
-
57
- post "/feature/:name/add_entity" do
58
- name, class_name, identifier = params.values_at("name", "class_name", "identifier")
59
-
60
- entity = Flipside.find_entity(class_name:, identifier:)
61
- Flipside.add_entity(name: , entity:)
62
- redirect to("/feature/#{name}/entities"), 303
63
- end
64
-
65
- post "/feature/:name/remove_entity" do
66
- Flipside.remove_entity(name: params["name"], entity_id: params["entity_id"])
67
- redirect to("/feature/#{params["name"]}/entities"), 303
68
- end
69
-
70
- get "/feature/:name/roles" do
71
- erb :feature_roles, locals: {feature: FeaturePresenter.new(feature, base_path)}
72
- end
73
-
74
- get '/search_role' do
75
- class_name = params[:class_name]
76
- query = URI.decode_www_form_component(params[:q])
77
- result = Flipside.search_role(class_name:, query:)
78
-
79
- erb :_search_result, locals: {result:, class_name:, query:}
80
- end
81
-
82
- post "/feature/:name/add_role" do
83
- name, class_name, method_name = params.values_at("name", "class_name", "identifier")
84
- Flipside.add_role(name:, class_name:, method_name:)
85
-
86
- redirect to("/feature/#{name}/roles"), 303
87
- end
88
-
89
- post "/feature/:name/remove_role" do
90
- Flipside.remove_role(name: params["name"], role_id: params["role_id"])
91
-
92
- redirect to("/feature/#{params["name"]}/roles"), 303
93
- end
53
+ r.on "feature", String do |name|
54
+ feature = load_feature(name)
55
+
56
+ r.is do
57
+ r.get do
58
+ view(:show, locals: { feature: FeaturePresenter.new(feature) })
59
+ end
60
+
61
+ r.put do
62
+ kwargs = r.params.slice("description", "activated_at", "deactivated_at")
63
+ feature.update(**kwargs)
64
+ r.redirect r.path, 303
65
+ end
66
+ end
67
+
68
+ r.put "toggle" do
69
+ if feature.update(enabled: !feature.enabled)
70
+ referer = r.env["HTTP_REFERER"]
71
+ r.redirect (referer || feature_path(feature)), 303
72
+ else
73
+ response.status = 422
74
+ "Failed to update feature"
75
+ end
76
+ end
77
+
78
+ r.get "entities" do
79
+ view(:feature_entities, locals: { feature: FeaturePresenter.new(feature) })
80
+ end
81
+
82
+ r.post "add_entity" do
83
+ class_name, identifier = r.params.values_at("class_name", "identifier")
84
+ entity = Flipside.find_entity(class_name:, identifier:)
85
+ Flipside.add_entity(feature:, entity:)
86
+ r.redirect feature_path(feature, "entities"), 303
87
+ end
88
+
89
+ r.post "remove_entity" do
90
+ Flipside.remove_entity(feature:, entity_id: r.params["entity_id"])
91
+ r.redirect feature_path(feature, "entities"), 303
92
+ end
93
+
94
+ r.get "roles" do
95
+ view(:feature_roles, locals: { feature: FeaturePresenter.new(feature) })
96
+ end
97
+
98
+ r.post "add_role" do
99
+ class_name, method_name = r.params.values_at("class_name", "identifier")
100
+ Flipside.add_role(name:, class_name:, method_name:)
101
+ r.redirect feature_path(feature, "roles"), 303
102
+ end
103
+
104
+ r.post "remove_role" do
105
+ Flipside.remove_role(name:, role_id: r.params["role_id"])
106
+ r.redirect feature_path(feature, "roles"), 303
107
+ end
108
+ end
94
109
 
95
- def feature
96
- @feature ||= Flipside::Feature.find_by!(name: params["name"])
97
- rescue
98
- halt 404, "This feature does not exist"
99
- end
110
+ r.get "search_entity" do
111
+ class_name = r.params["class_name"]
112
+ query = URI.decode_www_form_component(r.params["q"])
113
+ result = Flipside.search_entity(class_name:, query:)
114
+ view(:_search_result, locals: { result:, class_name:, query: })
115
+ end
100
116
 
101
- def base_path
102
- @base_path ||= request.script_name
117
+ r.get "search_role" do
118
+ class_name = r.params["class_name"]
119
+ query = URI.decode_www_form_component(r.params["q"])
120
+ result = Flipside.search_role(class_name:, query:)
121
+ view(:_search_result, locals: { result:, class_name:, query: })
122
+ end
103
123
  end
104
124
  end
105
125
  end
data/lib/flipside.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record"
3
4
  require "flipside/version"
4
5
  require "flipside/web"
5
6
  require "flipside/config/settings"
@@ -38,23 +39,23 @@ module Flipside
38
39
  feature.update(enabled: true)
39
40
  end
40
41
 
41
- def add_entity(name:, entity:)
42
- feature = find_by!(name:)
42
+ def add_entity(entity:, feature: nil, name: nil)
43
+ feature ||= find_by!(name:)
43
44
  Entity.find_or_create_by(feature:, flippable: entity)
44
45
  end
45
46
 
46
- def remove_entity(name:, entity_id:)
47
- feature = find_by!(name:)
47
+ def remove_entity(entity_id:, feature: nil, name: nil)
48
+ feature ||= find_by!(name:)
48
49
  feature.entities.find_by(id: entity_id)&.destroy
49
50
  end
50
51
 
51
- def add_role(name:, class_name:, method_name:)
52
- feature = find_by!(name:)
52
+ def add_role(class_name:, method_name:, feature: nil, name: nil)
53
+ feature ||= find_by!(name:)
53
54
  Role.find_or_create_by(feature:, class_name:, method: method_name)
54
55
  end
55
56
 
56
- def remove_role(name:, role_id:)
57
- feature = find_by!(name:)
57
+ def remove_role(role_id:, feature: nil, name: nil)
58
+ feature ||= find_by!(name:)
58
59
  feature.roles.find_by(id: role_id)&.destroy
59
60
  end
60
61
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flipside
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sammy Henningsson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-25 00:00:00.000000000 Z
11
+ date: 2025-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -25,7 +25,21 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '6.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: sinatra
28
+ name: roda
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tilt
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -52,6 +66,20 @@ dependencies:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rackup
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.2'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: rspec
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +108,20 @@ dependencies:
80
108
  - - ">="
81
109
  - !ruby/object:Gem::Version
82
110
  version: '2.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: puma
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '6.5'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '6.5'
83
125
  description: Create simple feature toggles.
84
126
  email:
85
127
  - sammy.henningsson@hey.com
@@ -105,11 +147,11 @@ files:
105
147
  - lib/flipside/config/roles.rb
106
148
  - lib/flipside/config/settings.rb
107
149
  - lib/flipside/feature_presenter.rb
150
+ - lib/flipside/importmap.rb
108
151
  - lib/flipside/public/index.js
109
152
  - lib/flipside/public/inline_edit_controller.js
110
153
  - lib/flipside/public/modal_controller.js
111
154
  - lib/flipside/public/search_controller.js
112
- - lib/flipside/public/toggle_controller.js
113
155
  - lib/flipside/search_result.rb
114
156
  - lib/flipside/version.rb
115
157
  - lib/flipside/views/_datetime_modal.erb
@@ -1,29 +0,0 @@
1
- import { Controller } from "@hotwired/stimulus";
2
-
3
- export default class ToggleController extends Controller {
4
- static targets = ["switch"]
5
- static values = {
6
- url: String,
7
- enabled: Boolean
8
- }
9
-
10
- async switch(event) {
11
- try {
12
- const data = {enable: !this.enabledValue}
13
- const response = await fetch(this.urlValue, {
14
- method: "PUT",
15
- headers: {"Content-Type": "application/json"},
16
- body: JSON.stringify(data)
17
- })
18
-
19
- if (response.ok) {
20
- location.reload()
21
- } else {
22
- const text = await response.text()
23
- console.error("Failed to update:", text)
24
- }
25
- } catch (error) {
26
- console.error("Error during the PUT request:", error)
27
- }
28
- }
29
- }