Skip to content

Conversation

@maebeale
Copy link
Collaborator

@maebeale maebeale commented Feb 4, 2026

  • This work Closes [link an issue]

What is the goal of this PR and why is this important?

Improve UX and change policy defaults

  • Update all form buttons to use the same code, and change tests to expect the new en.yml text
  • Change ApplicationPolicy to default everything to admin?
  • Add policies for the other models that have forms so allowed_to? will find the right policy
  • Rename public -> publicly_visible

How did you approach the change?

Anything else to add?

@@ -1,4 +1,5 @@
class ApplicationController < ActionController::Base
include ActionPolicy::Controller
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gives helper methods including allowed_to?, which we're now using on forms to determine if delete button should show

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
include ActionPolicy::Controller

Shouldn't need this explicitly. allowed_to? was already working for me and the doc's say its included in ActionController::Base

Were you getting an error without it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh interesting. yes, was getting an error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just removed it and everything still worked. TY for catching this!

@sectors = Sector.published
@windows_types = WindowsType.all
# @category_types = CategoryType.includes(:categories).published.decorate
@categories_grouped =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tags need the grouped categories, not @category_types

admin?
end

def recent?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not using this endpoint anymore


def index?
true
admin?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaulting to the most secure first, and then greenlighting everything else in other policies.

this avoids mistakes in forgetting to set admin-only indexes to admin-only. this way we'll need to set not-admin-only to visible.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

Can you add index? to the alias_rule manage?. This keeps them all set to admin? in one place and follows the convention from the Action Policy docs.

  alias_rule :index?, :new?, :create?, :edit?, :update?, :destroy?, to: :manage?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooo. great idea! yup, will do that now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding both index? and show?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maebeale might as well!


def show?
admin? || record.published?
admin?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing here -- defaulting to lockdown mode and we'll greenlight access in other policies


def admin?
user&.super_user?
user&.admin?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trying to remove/minimize super_user from the code, as i'm sure we'll be moving to a different permissioning implementation once we have more roles.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it makes more sense to have the user permission checked here as this this the authorization framework? The method in User model works but is that the best place?

The hope is that any check we make for an admin? is via action_policy not directly off current_user

I agree only one place for sure if we can help it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like better using it here.

and removing all the places we say (...user.super_user or even ...user.admin) and instead use a policy.

# true
# end
#
def show?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have UI for non-admin banner show, but, technically we would be fine w users seeing it.


relation_scope do |relation|
next relation if admin?
relation.where(user: user)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

users can only see their own bookmarks on their /personal page

if turbo_frame_request?
per_page = params[:number_of_items_per_page].presence || 12
unfiltered = current_user.super_user? ? CommunityNews.all : CommunityNews.published
unfiltered =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need to do this if we're using scopes in policies?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to display the count of the results vs total. e.g. 5/20 we need the unfiltered number. If we want to keep it simple we could remove the total.

"Total" gets a bit messy. Total of all? total for the current user? Do we include hidden for admin?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it is getting messy. i think we'd want to say total for the current user (or guest)?

scope :community_news_name, ->(community_news_name) {
community_news_name.present? ? where("community_news.name LIKE ?", "%#{community_news_name}%") : all }
scope :featured, -> { where(featured: true) }
scope :public_featured, -> { published.where(public_featured: true) }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prepending published to all scopes for public_featured and publicly_visible

</div>

<div>
<div class="action-buttons mt-8 flex justify-center gap-3">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this class set to all form action sections

<%= link_to "Cancel", banners_path, class: "btn btn-secondary-outline" %>

<%= f.button :submit,
(@banner.new_record? ? "Create Banner" : "Update Banner"),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't need conditional text if using f.button :submit and en.yml

class: "btn btn-danger-outline",
data: { confirm: "Are you sure you want to delete this banner?" } %>
<div class="action-buttons mt-8 flex justify-center gap-3">
<% if f.object.persisted? && allowed_to?(:destroy?, f.object) %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicitly checking destroy permission before displaying delete button


<% if @event.errors.any? %>
<%= render 'shared/errors', resource: @event %>
<%= render "shared/errors", resource: @event %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some ' -> " changes throughout

<%= f.input :short_name,
input_html: { readonly: true },
as: :string %>
<% if params[:admin] || !f.object.persisted? %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decided to offer an easter egg to change short_name from the UI


<%
=begin %>
<%# begin %>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why there's begin and end tags in here...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something was broken for me when I was fixing the workshop promotion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so it can be removed now?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, just need to check for error's on the page and flow but that can be a separate issue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'll leave it in there for you to remove when you're ready

=end %>
<%# =end %>

<div class="my-4"><%= render "assets/form", owner: workshop_idea %></div>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmilljr24 this doesn't seem broken when moved?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In html you are not supposed to have a form nested within a form. It's hard to see on github where the main form ends so I can't tell exactly where you moved it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it is within it...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i moved it back out

project: "Organization" No newline at end of file
project: "Organization"
helpers:
submit:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where all the f.button :submit text will come from now

@@ -0,0 +1,6 @@
class AddPubliclyVisibleToModels < ActiveRecord::Migration[8.1]
def change
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding publicly_visible to the two help-related tables too

# --- Traits for type-specific subclasses ---
factory :primary_asset, class: "PrimaryAsset"
factory :gallery_assets, class: "GalleryAsset"
factory :gallery_asset, class: "GalleryAsset"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

factories are always singularly named

registration_close_date { 13.days.from_now }
inactive { false }
cost_cents { 1099 }
public { false }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setting this to public false to start to make tests better match default state of the object

it "checks authorization via ApplicationPolicy" do
expect_any_instance_of(ApplicationPolicy)
.to receive(:index?).and_call_original
it "checks authorization via TagPolicy" do
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a TagPolicy, to referring to that now

visit event_path(event)

# Title (uses title_with_badges)
expect(page).to have_css("div.my-3", text: "My Event")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removing testing on specific css

end

# Times block exists
expect(page).to have_text("Start:")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this text isn't on the page anymore

create(:event_registration, event: event, registrant: user)
end

it "shows De-register button + registered badge + calendar links" do
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chatgpt changes


# READONLY FIELDS (since this is used in code, we're not exposing it to the user)
assert_select "input[name=?][readonly]", "windows_type[short_name]"
assert_select "input[name=?]", "windows_type[short_name]"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not readonly when new, since it's a required field

if turbo_frame_request?
per_page = params[:number_of_items_per_page].presence || 12
unfiltered = current_user.super_user? ? CommunityNews.all : CommunityNews.published
unfiltered =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to display the count of the results vs total. e.g. 5/20 we need the unfiltered number. If we want to keep it simple we could remove the total.

"Total" gets a bit messy. Total of all? total for the current user? Do we include hidden for admin?

@@ -1,4 +1,5 @@
class ApplicationController < ActionController::Base
include ActionPolicy::Controller
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
include ActionPolicy::Controller

Shouldn't need this explicitly. allowed_to? was already working for me and the doc's say its included in ActionController::Base

Were you getting an error without it?


def index?
true
admin?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

Can you add index? to the alias_rule manage?. This keeps them all set to admin? in one place and follows the convention from the Action Policy docs.

  alias_rule :index?, :new?, :create?, :edit?, :update?, :destroy?, to: :manage?


def admin?
user&.super_user?
user&.admin?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it makes more sense to have the user permission checked here as this this the authorization framework? The method in User model works but is that the best place?

The hope is that any check we make for an admin? is via action_policy not directly off current_user

I agree only one place for sure if we can help it.


<%
=begin %>
<%# begin %>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something was broken for me when I was fixing the workshop promotion.

=end %>
<%# =end %>

<div class="my-4"><%= render "assets/form", owner: workshop_idea %></div>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In html you are not supposed to have a form nested within a form. It's hard to see on github where the main form ends so I can't tell exactly where you moved it.

hint: "Any tips on how to make this workshop a success") %>
<% end %>

<%= render "shared/form_dropdown", form: f, field: :extra_field, label: "Extra Field" do %>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason I had this at the top was because of the order displayed on the show page. Extra Field is the first thing displayed. We should ask what order they expect.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i want to leave it at the bottom of the edit screen, and still display it at the top on show. then it's not their first instinct, AND, we still allow them to insert something above everything else?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That make sense. There are workshops where they literally have pages worth of content all in "extra_field" which doesn't make sense.

As long as they know where the field will be displayed that make sense to have it as a secondary option.

included do
scope :featured, -> { where(featured: true, inactive: false) }
scope :public_featured, -> { where(public_featured: true, public: true, inactive: false) }
scope :featured, -> { published.where(featured: true) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for cleaning this up. I wanted to get most of the similar scope into a concern but the inactive vs published on different models side tracked me and this is where I left off.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm taking a second pass on all of these booleans and scopes and trying to normalize inactive/published

@maebeale maebeale marked this pull request as ready for review February 4, 2026 19:47
per_page = params[:number_of_items_per_page].presence || 12
unfiltered = current_user.super_user? ? CommunityNews.all : CommunityNews.published
unfiltered =
if current_user&.admin?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed this.

To use the relation_scope in the policy you need to call authorized_scope(CommunityNews.all)

That will look in the Community News Policy for the relation_scope. authorized_scope allows other options like specifying which policy or a specific named scope withing a policy as well if you need it.

@maebeale maebeale merged commit a225923 into main Feb 5, 2026
3 checks passed
@maebeale maebeale deleted the update-button-text branch February 5, 2026 02:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants