Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
## [Unreleased]
## [0.4.0] - 2026-09-25
- Show entities whose record has been deleted instead of raising in the UI
- 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
- Add `Flipside.prune_orphaned_entities`
- Registering the same role twice no longer lists it twice
- Display STI subclass entities through their own or their base class's registration
- Verify `Flipside.flippables` at boot when a Rails app eager loads

### Deprecated
- `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.
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
flipside (0.3.4)
flipside (0.4.0)
activerecord (>= 6.0)
roda
tilt
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ Entities can be added to a feature by searching for records.

![Add an entity](/add_entity.png)

`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.
`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.
```ruby
class User < ApplicationRecord
include Flipside::Flippable
Expand Down
24 changes: 19 additions & 5 deletions lib/flipside/config/entities.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ def display_entity(entity)

if record.nil?
"#{label} (deleted)"
elsif registered_entities.key?(record.class.to_s)
display_flippable(record)
elsif (registration = registration_for(record.class))
registration.display(record)
else
label
end
Expand All @@ -58,9 +58,23 @@ def display_entity(entity)
private

def display_flippable(record)
registered_entities
.fetch(record.class.to_s)
.display(record)
registration = registration_for(record.class)
raise KeyError, "#{record.class} is not registered with Flipside" unless registration

registration.display(record)
end

# The registration of the class or its closest registered superclass, so
# an STI subclass is displayed through the base class's registration
# unless it has one of its own.
def registration_for(klass)
klass.ancestors.each do |ancestor|
next unless ancestor.is_a?(Class)

registration = registered_entities[ancestor.name]
return registration if registration
end
nil
end

def registered_entities
Expand Down
7 changes: 7 additions & 0 deletions lib/flipside/config/flippables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ def register_flippable(class_name) # :nodoc:
registered_flippables << class_name.to_s
end

# Raises unless Flipside.flippables and the classes calling the
# Flipside::Flippable macros agree. Runs whenever the UI reads the
# registry, and at boot in an app that eager loads (see Railtie).
def verify_flippables! # :nodoc:
load_flippables
end

private

def load_flippables
Expand Down
13 changes: 11 additions & 2 deletions lib/flipside/railtie.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
module Flipside
# Hands Flipside's deprecator to the host app, so its deprecation settings
# (log, raise, silence) apply to Flipside's warnings too.
# Wires Flipside into a Rails app. Only loaded when Rails is present.
class Railtie < ::Rails::Railtie
# Hands Flipside's deprecator to the app, so its deprecation settings (log,
# raise, silence) apply to Flipside's warnings too.
initializer "flipside.deprecator" do |app|
app.deprecators[:flipside] = Flipside.deprecator if app.respond_to?(:deprecators)
end

# Eager loading runs before after_initialize, so every class calling the
# macros has registered by now. Checking here fails boot, and so CI, for a
# misconfigured list instead of the first visit to the UI. Without eager
# loading the check would only see whatever happens to be loaded.
config.after_initialize do |app|
Flipside.verify_flippables! if app.config.eager_load
end
end
end
2 changes: 1 addition & 1 deletion lib/flipside/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Flipside
VERSION = "0.3.4"
VERSION = "0.4.0"
end
16 changes: 16 additions & 0 deletions spec/flipside/flippable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ def define_user(&block)
end
end

describe "Flipside.verify_flippables!" do
it "passes when the list matches the classes using the macros" do
Flipside.flippables = ["User"]
define_user { flipside_role(:admin?) }

expect { Flipside.verify_flippables! }.not_to raise_error
end

it "raises for a class using the macros that is not listed" do
define_user { flipside_role(:admin?) }

expect { Flipside.verify_flippables! }
.to raise_error(Flipside::Error, "User must be listed in Flipside.flippables")
end
end

it "registers without deprecation warnings" do
expect(Flipside.deprecator).not_to receive(:warn)
Flipside.flippables = ["User"]
Expand Down
13 changes: 13 additions & 0 deletions spec/flipside_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,19 @@ def admin?
expect(Flipside.display_entity(entity.reload)).to eq("User ##{user.id} (deleted)")
end

it "displays an STI subclass through the registration of its base class" do
ActiveRecord::Base.connection.add_column(:users, :type, :string)
User.reset_column_information
stub_const("Admin", Class.new(User))
Flipside.add_registered_entity(class_name: "User", search_by: nil, display_as: :name)
feature = Feature.create!(name: "some_feature")
admin = Admin.create!(name: "John Doe")
entity = Entity.create!(feature:, flippable: admin)

expect(Flipside.display_entity(entity.reload)).to eq("John Doe")
expect(Flipside.display_entity(admin)).to eq("John Doe")
end

it "displays an entity whose flippable is an STI subclass" do
ActiveRecord::Base.connection.add_column(:users, :type, :string)
User.reset_column_information
Expand Down
Loading