flipside 0.3.3 → 0.4.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: a1bae223d284c0485444acb094ff4b10b6408e454c4906d00a22fc7ebd4861ef
4
- data.tar.gz: 6df03049c53ded14ecdbc6106c9a78031e4c3a0f7bef9bc2abeef901a07c40c6
3
+ metadata.gz: a9f532913a357e7f57e0c8c25027ac902d18b41f28ee43481675b320ce2b64b1
4
+ data.tar.gz: 9fd4fe7cf2de639c2730cdab1115d952b7bd98fb91add5b0c19df021fa702ded
5
5
  SHA512:
6
- metadata.gz: f81d8c85096df462fa7143a01587a36b8cf74a7ccf528dc52257653ec93a8cd4e24bd8d302cae9f963eb26f4c25cd369ac4d32b64713de7d9c784a861bd12b26
7
- data.tar.gz: 41c355c419cf5dbb4baa33d85a17030d22a1cca4f4a90aade2094eedaefe33099c52d7c165f81e67f5bfcfb2b2744970530bedcdb904246a35dffa56896450da
6
+ metadata.gz: 4ff600b3a1d15051fe63d7c372c04ef03486271f4ccc0de3305ae1a052745553894389ed0989f596814c78142b211a342b4755d9d35b5b1db002b274171254bb
7
+ data.tar.gz: 778c918d6b61bc4dd24407fadd5476657e382f35a36c1ee660d980e4f1b119d5f3ad55e366dd939234a6ce95142aed8b6815e45d0cefc2a5c1baa22a6775237c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.4.0] - 2026-09-25
2
+ - Show entities whose record has been deleted instead of raising in the UI
3
+ - Add `Flipside::Flippable` with `flipside_entity` and `flipside_role`, so models can register themselves (listed in `Flipside.flippables`); entities of a destroyed record are removed
4
+ - Add `Flipside.prune_orphaned_entities`
5
+ - Registering the same role twice no longer lists it twice
6
+ - Display STI subclass entities through their own or their base class's registration
7
+ - Verify `Flipside.flippables` at boot when a Rails app eager loads
8
+
9
+ ### Deprecated
10
+ - `Flipside.register_entity` and `Flipside.register_role`, in favour of `flipside_entity` and `flipside_role` from `Flipside::Flippable`. They will be removed in 1.0.
11
+
12
+ ## [0.3.4] - 2026-03-04
13
+ - Support an array of default objects
14
+
1
15
  ## [0.3.3] - 2025-08-21
2
16
  - Move gem ownership
3
17
 
data/README.md CHANGED
@@ -87,16 +87,23 @@ We can also check multiple records. `.enabled?` will return `true` if any of the
87
87
  Flipside.enabled? "MyFeature", user, company, location
88
88
  ```
89
89
 
90
- ### Enabling features for specific records
90
+ #### Enabling features for specific records
91
91
 
92
92
  Features can be enabled for a certain record, typically a certain user or organization. These records are called entities. To enable a feature for a given record use `.add_entity`:
93
93
  ```ruby
94
94
  user = User.first
95
95
  Flipside.enabled? "MyFeature", user # => false
96
- Flipside.add_entity(name: "MyFeature", user)
96
+ Flipside.add_entity(name: "MyFeature", entity: user)
97
97
  Flipside.enabled? "MyFeature", user # => true
98
98
  ```
99
99
 
100
+ Entities reference their record polymorphically, so there is no foreign key to clean them up when the record is deleted. A model registered with `flipside_entity` (see [Registering from the model](#registering-from-the-model)) removes its entities when a record is destroyed.
101
+
102
+ Entities left behind by records deleted without callbacks (or before the concern was added) can be removed with:
103
+ ```ruby
104
+ Flipside.prune_orphaned_entities # => number of entities deleted
105
+ ```
106
+
100
107
  Features can be enabled for records responding true to a certain method. This is called a "role". Given that User records have an admin? method. A feature can then be enabled
101
108
  for all users who are admins, using the `.add_role` method:
102
109
  ```ruby
@@ -123,9 +130,9 @@ Note: you probably want to wrap this inside a constraints block to provide some
123
130
  ![UI](/features.png)
124
131
 
125
132
 
126
- ### Configuration
133
+ ## Configuration
127
134
 
128
- The Flipside UI can be configured by calling some class methods on `Flipside` (see below).
135
+ Flipside can be configured by calling some class methods on `Flipside` (see below).
129
136
 
130
137
  `ui_back_path` is used to set a path to return to from the Flipside UI. If this is set, then the UI shows a "Back" button,
131
138
  targeting this path/url. By default no back button is shown.
@@ -149,7 +156,12 @@ Flipside.enabled? :some_feature
149
156
  # Which will be the same as
150
157
  Flipside.enabled? :some_feature, Current.user
151
158
 
152
- # Note: if we do pass in an argument, then `default_object ` will not be used:
159
+ # If `default_object` is an Array, then each element is checked if the feature is enabled.
160
+ ```ruby
161
+ Flipside.default_object = -> { [Current.user, Current.organization] }
162
+ ```
163
+
164
+ #### Note: if we do pass in an argument, then `default_object ` will not be used:
153
165
  Flipside.enabled? :some_feature, Current.company # check current company instead of user.
154
166
  ```
155
167
 
@@ -165,34 +177,51 @@ Flipside.create_missing_features = true
165
177
  Flipside.default_object = -> { Current.user }
166
178
  ```
167
179
 
168
- #### Entities
180
+ ### Registering from the model
181
+
182
+ Entities and roles are searched for in the UI, so Flipside needs to know which classes can be used. A model registers itself by including `Flipside::Flippable` and calling `flipside_entity` and/or `flipside_role`:
183
+ ```ruby
184
+ class User < ApplicationRecord
185
+ include Flipside::Flippable
186
+
187
+ flipside_entity search_by: :name, display_as: :name
188
+ flipside_role :admin?
189
+ flipside_role :awesome?, display_as: "Awesome users"
190
+ end
191
+ ```
192
+
193
+ List the class names in an initializer, so that Flipside can load them when the UI needs them (also when the app does not eager load):
194
+ ```ruby
195
+ # config/initializers/flipside.rb
196
+ Flipside.flippables = %w[User]
197
+ ```
198
+
199
+ A class using these macros must be listed in `Flipside.flippables`, and a listed class must call at least one of them, otherwise Flipside raises.
200
+
201
+ ### Entities
169
202
 
170
203
  Entities can be added to a feature by searching for records.
171
204
 
172
205
  ![Add an entity](/add_entity.png)
173
206
 
174
- To make this work, some configuration is required. Use the class method `Flipside.register_entity` for this.
207
+ `flipside_entity` should be called in each class that may be used as a feature enabler. It also removes the entities of a record when it is destroyed. Note that this includes soft deletion that runs destroy callbacks (e.g. paranoia), so a restored record comes back without its features. Soft deletion that skips them (e.g. discard) keeps the entities.
175
208
  ```ruby
176
- Flipside.register_entity(
177
- class_name: "User",
178
- search_by: :name,
179
- display_as: :name,
180
- identified_by: :id
181
- )
209
+ class User < ApplicationRecord
210
+ include Flipside::Flippable
211
+
212
+ flipside_entity search_by: :name, display_as: :name, identified_by: :id
213
+ end
182
214
  ```
183
215
 
184
- The `.register_entity` method should be called once for each class that may be used as a feature enabler.
185
216
  The `search_by` keyword argument, which may be a `Symbol` or a `Proc`, dictates how records are found from searching in the ui.
186
217
  When a `Symbol` is given, e.g. `:name`, then entities with an exact match on the corresponding attribute are returned. I.e. `User.where(name: query)`.
187
218
  When a `Proc` is given, then this `Proc` is called with the search string and is expected to return an object responding to `to_a` (e.g. an AR collection).
188
219
  This gives us the flexibility to decide how to search for entities. For example, to search for users with matching first name or last name or an email
189
220
  starting with _query_, something like this could be used.
190
221
  ```ruby
191
- Flipside.register_entity(
192
- class_name: "User",
193
- search_by: ->(str) { User.where("lower(first_name) = :name OR lower(last_name) = :name or email LIKE :str", name: str.downcase, str: "#{str}%") },
194
- )
195
-
222
+ flipside_entity search_by: ->(str) {
223
+ where("lower(first_name) = :name OR lower(last_name) = :name or email LIKE :str", name: str.downcase, str: "#{str}%")
224
+ }
196
225
  ```
197
226
 
198
227
  The `identified_by` keyword argument, sets the column used as primary key for the corresponding table. This defaults to `:id` and typically does need to be change.
@@ -202,38 +231,72 @@ The `display_as` keyword argument, is used to configure how these entities show
202
231
  For example, given the following setup. Users will be displayed with first name and last name:
203
232
  ```ruby
204
233
  class User < ApplicationRecord
234
+ include Flipside::Flippable
235
+
236
+ flipside_entity display_as: :name
237
+
205
238
  def name
206
239
  [first_name, last_name].compact.map(&:capitalize).join(" ")
207
240
  end
208
241
  end
209
-
210
- Flipside.register_entity(
211
- class_name: "User",
212
- display_as: :name,
213
- )
214
242
  ```
215
243
 
216
244
  When a `Proc` is given, then it is expected to take an entity as input and return a string used for displaying the entity. The config above could then instead be done using:
217
245
  ```ruby
218
- Flipside.register_entity(
219
- class_name: "User",
220
- display_as: ->(user) { [user.first_name, user.last_name].compact.map(&:capitalize).join(" ") }
221
- )
246
+ flipside_entity display_as: ->(user) { [user.first_name, user.last_name].compact.map(&:capitalize).join(" ") }
222
247
  ```
223
248
 
224
- #### Roles
249
+ ### Roles
225
250
 
226
251
  Features can be enabled for certain roles, by searching for roles (by method name).
227
252
 
228
253
  ![Add a role](/add_role.png)
229
254
 
230
- This is configured by calling the class method `Flipside.register_role` for each role to be added.
231
- Note a role consists of a class and a corresponding instance method.
255
+ This is configured by calling `flipside_role` for each role to be added. A role consists of a class and a corresponding instance method, and `display_as` optionally sets how it shows up in the UI.
256
+ ```ruby
257
+ class User < ApplicationRecord
258
+ include Flipside::Flippable
259
+
260
+ flipside_role :admin?
261
+ flipside_role :awesome?, display_as: "Awesome users"
262
+ end
263
+ ```
264
+
265
+ ### Classes you cannot edit
266
+
267
+ A class from another gem can be registered by including the concern from `to_prepare`, which runs at boot and again after every reload. It must still be listed in `Flipside.flippables`:
232
268
  ```ruby
269
+ # config/initializers/flipside.rb
270
+ Flipside.flippables = %w[User OtherGem::Account]
271
+
272
+ Rails.application.config.to_prepare do
273
+ OtherGem::Account.include(Flipside::Flippable)
274
+ OtherGem::Account.flipside_entity(display_as: :name)
275
+ end
276
+ ```
277
+
278
+ ### Upgrading from `register_entity` and `register_role`
279
+
280
+ `Flipside.register_entity` and `Flipside.register_role` are deprecated and will be removed in Flipside 1.0. Move each call into its model:
281
+ ```ruby
282
+ # Before, in config/initializers/flipside.rb
283
+ Flipside.register_entity(class_name: "User", search_by: :name, display_as: :name)
233
284
  Flipside.register_role(class_name: "User", method_name: :admin?)
234
- Flipside.register_role(class_name: "User", method_name: :awesome?)
285
+
286
+ # After, in config/initializers/flipside.rb
287
+ Flipside.flippables = %w[User]
288
+
289
+ # and in app/models/user.rb
290
+ class User < ApplicationRecord
291
+ include Flipside::Flippable
292
+
293
+ flipside_entity search_by: :name, display_as: :name
294
+ flipside_role :admin?
295
+ end
235
296
  ```
236
297
 
298
+ In a Rails 7.1+ app the deprecation warnings follow the app's own deprecation settings (`Rails.application.deprecators[:flipside]`).
299
+
237
300
  ## Development
238
301
 
239
302
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rspec` to run the tests.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+ require "models/flipside/entity"
5
+
6
+ module Flipside
7
+ # Lets a model register itself with Flipside. The class must also be listed in
8
+ # Flipside.flippables, so that Flipside can load it when the UI needs it. The
9
+ # listing is checked when Flipside loads the classes rather than here, since
10
+ # a model may be loaded before the initializer that sets Flipside.flippables.
11
+ module Flippable
12
+ extend ActiveSupport::Concern
13
+
14
+ class_methods do
15
+ # Registers the class as an entity and removes its Flipside::Entity rows
16
+ # when a record is destroyed.
17
+ def flipside_entity(search_by: nil, display_as: nil, identified_by: :id)
18
+ register_flippable!
19
+
20
+ has_many :flipside_entities,
21
+ class_name: "Flipside::Entity",
22
+ as: :flippable,
23
+ dependent: :delete_all
24
+
25
+ Flipside.add_registered_entity(class_name: name, search_by:, display_as:, identified_by:)
26
+ end
27
+
28
+ def flipside_role(method_name, display_as: nil)
29
+ register_flippable!
30
+
31
+ Flipside.add_registered_role(class_name: name, method_name:, display_as:)
32
+ end
33
+
34
+ private
35
+
36
+ def register_flippable!
37
+ raise Flipside::Error, "Flipside macros need a named class" if name.nil?
38
+
39
+ Flipside.register_flippable(name)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -4,6 +4,15 @@ module Flipside
4
4
  module Config
5
5
  module Entities
6
6
  def register_entity(class_name:, search_by:, display_as:, identified_by: :id)
7
+ deprecator.warn(
8
+ "Flipside.register_entity is deprecated and will be removed in Flipside " \
9
+ "#{deprecator.deprecation_horizon}. Include Flipside::Flippable in " \
10
+ "#{class_name}, call flipside_entity there and list it in Flipside.flippables."
11
+ )
12
+ add_registered_entity(class_name:, search_by:, display_as:, identified_by:)
13
+ end
14
+
15
+ def add_registered_entity(class_name:, search_by:, display_as:, identified_by: :id) # :nodoc:
7
16
  registered_entities[class_name.to_s] = RegisteredEntity.new(
8
17
  class_name:,
9
18
  search_by:,
@@ -13,25 +22,61 @@ module Flipside
13
22
  end
14
23
 
15
24
  def entity_classes
25
+ load_flippables
16
26
  registered_entities.keys
17
27
  end
18
28
 
19
29
  def search_entity(class_name:, query:)
30
+ load_flippables
20
31
  registered_entities.fetch(class_name.to_s).search(query)
21
32
  end
22
33
 
23
34
  def find_entity(class_name:, identifier:)
35
+ load_flippables
24
36
  registered_entities.fetch(class_name.to_s).find(identifier)
25
37
  end
26
38
 
39
+ # Accepts either a flippable record or a Flipside::Entity join row. Given
40
+ # a join row, it still renders when the record or its class has been
41
+ # deleted, or the class is no longer registered, so the UI can list it
42
+ # for removal.
27
43
  def display_entity(entity)
28
- registered_entities
29
- .fetch(entity.class.to_s)
30
- .display(entity)
44
+ return display_flippable(entity) unless entity.is_a?(Flipside::Entity)
45
+
46
+ record = entity.flippable if entity.flippable_type.safe_constantize
47
+ label = "#{entity.flippable_type} ##{entity.flippable_id}"
48
+
49
+ if record.nil?
50
+ "#{label} (deleted)"
51
+ elsif (registration = registration_for(record.class))
52
+ registration.display(record)
53
+ else
54
+ label
55
+ end
31
56
  end
32
57
 
33
58
  private
34
59
 
60
+ def display_flippable(record)
61
+ registration = registration_for(record.class)
62
+ raise KeyError, "#{record.class} is not registered with Flipside" unless registration
63
+
64
+ registration.display(record)
65
+ end
66
+
67
+ # The registration of the class or its closest registered superclass, so
68
+ # an STI subclass is displayed through the base class's registration
69
+ # unless it has one of its own.
70
+ def registration_for(klass)
71
+ klass.ancestors.each do |ancestor|
72
+ next unless ancestor.is_a?(Class)
73
+
74
+ registration = registered_entities[ancestor.name]
75
+ return registration if registration
76
+ end
77
+ nil
78
+ end
79
+
35
80
  def registered_entities
36
81
  @registered_entities ||= {}
37
82
  end
@@ -0,0 +1,46 @@
1
+ module Flipside
2
+ module Config
3
+ # Names of the classes that register themselves through the
4
+ # Flipside::Flippable macros. Listing them lets Flipside load them on demand,
5
+ # so the UI finds them whether or not the app eager loads.
6
+ module Flippables
7
+ attr_writer :flippables
8
+
9
+ def flippables
10
+ @flippables ||= []
11
+ end
12
+
13
+ def register_flippable(class_name) # :nodoc:
14
+ registered_flippables << class_name.to_s
15
+ end
16
+
17
+ # Raises unless Flipside.flippables and the classes calling the
18
+ # Flipside::Flippable macros agree. Runs whenever the UI reads the
19
+ # registry, and at boot in an app that eager loads (see Railtie).
20
+ def verify_flippables! # :nodoc:
21
+ load_flippables
22
+ end
23
+
24
+ private
25
+
26
+ def load_flippables
27
+ flippables.each do |name|
28
+ name.constantize
29
+ next if registered_entities.key?(name) || registered_roles.key?(name)
30
+
31
+ raise Error, "#{name} is listed in Flipside.flippables but calls neither " \
32
+ "flipside_entity nor flipside_role"
33
+ end
34
+
35
+ unlisted = registered_flippables.to_a - flippables
36
+ return if unlisted.empty?
37
+
38
+ raise Error, "#{unlisted.join(", ")} must be listed in Flipside.flippables"
39
+ end
40
+
41
+ def registered_flippables
42
+ @registered_flippables ||= Set.new
43
+ end
44
+ end
45
+ end
46
+ end
@@ -4,8 +4,17 @@ module Flipside
4
4
  module Config
5
5
  module Roles
6
6
  def register_role(class_name:, method_name:, display_as: nil)
7
- registered_roles[class_name.to_s] ||= []
8
- registered_roles[class_name.to_s] << RegisteredRole.new(
7
+ deprecator.warn(
8
+ "Flipside.register_role is deprecated and will be removed in Flipside " \
9
+ "#{deprecator.deprecation_horizon}. Include Flipside::Flippable in " \
10
+ "#{class_name}, call flipside_role there and list it in Flipside.flippables."
11
+ )
12
+ add_registered_role(class_name:, method_name:, display_as:)
13
+ end
14
+
15
+ def add_registered_role(class_name:, method_name:, display_as: nil) # :nodoc:
16
+ registered_roles[class_name.to_s] ||= {}
17
+ registered_roles[class_name.to_s][method_name.to_s] = RegisteredRole.new(
9
18
  class_name:,
10
19
  method_name:,
11
20
  display_as:
@@ -13,11 +22,13 @@ module Flipside
13
22
  end
14
23
 
15
24
  def role_classes
25
+ load_flippables
16
26
  registered_roles.keys
17
27
  end
18
28
 
19
29
  def search_role(class_name:, query:)
20
- registered_roles.fetch(class_name.to_s).filter_map do |registered_role|
30
+ load_flippables
31
+ registered_roles.fetch(class_name.to_s).values.filter_map do |registered_role|
21
32
  next unless registered_role.match? query
22
33
  registered_role.to_result
23
34
  end
@@ -0,0 +1,18 @@
1
+ module Flipside
2
+ # Wires Flipside into a Rails app. Only loaded when Rails is present.
3
+ class Railtie < ::Rails::Railtie
4
+ # Hands Flipside's deprecator to the app, so its deprecation settings (log,
5
+ # raise, silence) apply to Flipside's warnings too.
6
+ initializer "flipside.deprecator" do |app|
7
+ app.deprecators[:flipside] = Flipside.deprecator if app.respond_to?(:deprecators)
8
+ end
9
+
10
+ # Eager loading runs before after_initialize, so every class calling the
11
+ # macros has registered by now. Checking here fails boot, and so CI, for a
12
+ # misconfigured list instead of the first visit to the UI. Without eager
13
+ # loading the check would only see whatever happens to be loaded.
14
+ config.after_initialize do |app|
15
+ Flipside.verify_flippables! if app.config.eager_load
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flipside
4
- VERSION = "0.3.3"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -51,7 +51,7 @@
51
51
  <li class="mt-2 p-2 bg-slate-700 text-slate-300 border border-slate-600 rounded-lg">
52
52
  <div class="flex justify-between items-center">
53
53
  <div>
54
- <%= Flipside.display_entity(entity.flippable) %> (<%= entity.flippable_type %>)
54
+ <%= Flipside.display_entity(entity) %> (<%= entity.flippable_type %>)
55
55
  </div>
56
56
  <div>
57
57
  <form action="<%= feature_path(feature, "remove_entity") %>" method="post">
data/lib/flipside.rb CHANGED
@@ -1,17 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record"
4
+ require "active_support/deprecation"
4
5
  require "flipside/version"
5
6
  require "flipside/web"
6
7
  require "flipside/config/settings"
7
8
  require "flipside/config/entities"
8
9
  require "flipside/config/roles"
10
+ require "flipside/config/flippables"
9
11
  require "models/flipside/feature"
12
+ require "models/flipside/flippable"
13
+ require "flipside/railtie" if defined?(Rails::Railtie)
10
14
 
11
15
  module Flipside
12
16
  extend Config::Settings
13
17
  extend Config::Entities
14
18
  extend Config::Roles
19
+ extend Config::Flippables
15
20
 
16
21
  class Error < StandardError; end
17
22
 
@@ -22,11 +27,16 @@ module Flipside
22
27
  end
23
28
 
24
29
  class << self
30
+ def deprecator
31
+ @deprecator ||= ActiveSupport::Deprecation.new("1.0", "Flipside")
32
+ end
33
+
25
34
  def enabled?(name, *objects)
26
35
  feature = find_by(name:)
27
36
  return false unless feature
28
37
 
29
- objects = [default_object] if objects.empty?
38
+ objects = Array(default_object) if objects.empty?
39
+ objects << nil if objects.empty?
30
40
  objects.any? { |object| feature.enabled? object }
31
41
  end
32
42
 
@@ -39,6 +49,11 @@ module Flipside
39
49
  feature.update(enabled: true)
40
50
  end
41
51
 
52
+ def disable!(name)
53
+ feature = find_by!(name:)
54
+ feature.update(enabled: false)
55
+ end
56
+
42
57
  def add_entity(entity:, feature: nil, name: nil)
43
58
  feature ||= find_by!(name:)
44
59
  Entity.find_or_create_by(feature:, flippable: entity)
@@ -49,6 +64,22 @@ module Flipside
49
64
  feature.entities.find_by(id: entity_id)&.destroy
50
65
  end
51
66
 
67
+ # Deletes entities whose record no longer exists, or whose class does not.
68
+ # Returns the number of entities deleted.
69
+ def prune_orphaned_entities
70
+ Entity.distinct.pluck(:flippable_type).sum do |type|
71
+ entities = Entity.where(flippable_type: type)
72
+ klass = type.safe_constantize
73
+ next entities.delete_all unless klass
74
+
75
+ existing = klass
76
+ .unscoped
77
+ .where(klass.primary_key => entities.pluck(:flippable_id))
78
+ .pluck(klass.primary_key)
79
+ entities.where.not(flippable_id: existing).delete_all
80
+ end
81
+ end
82
+
52
83
  def add_role(class_name:, method_name:, feature: nil, name: nil)
53
84
  feature ||= find_by!(name:)
54
85
  Role.find_or_create_by(feature:, class_name:, method: method_name)
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.3.3
4
+ version: 0.4.0
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-08-21 00:00:00.000000000 Z
11
+ date: 2026-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -136,12 +136,14 @@ files:
136
136
  - app/models/flipside/entities.rb
137
137
  - app/models/flipside/entity.rb
138
138
  - app/models/flipside/feature.rb
139
+ - app/models/flipside/flippable.rb
139
140
  - app/models/flipside/key.rb
140
141
  - app/models/flipside/keys.rb
141
142
  - app/models/flipside/role.rb
142
143
  - app/models/flipside/roles.rb
143
144
  - lib/flipside.rb
144
145
  - lib/flipside/config/entities.rb
146
+ - lib/flipside/config/flippables.rb
145
147
  - lib/flipside/config/registered_entity.rb
146
148
  - lib/flipside/config/registered_role.rb
147
149
  - lib/flipside/config/roles.rb
@@ -152,6 +154,7 @@ files:
152
154
  - lib/flipside/public/inline_edit_controller.js
153
155
  - lib/flipside/public/modal_controller.js
154
156
  - lib/flipside/public/search_controller.js
157
+ - lib/flipside/railtie.rb
155
158
  - lib/flipside/search_result.rb
156
159
  - lib/flipside/version.rb
157
160
  - lib/flipside/views/_datetime_modal.erb