From bea2c89c2b3b06d67f0a250ed46666405a527214 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 27 Nov 2025 17:15:26 +0100 Subject: [PATCH 01/25] Add new has_json to add Account#settings to restrict room creation to only administrators --- app/controllers/accounts_controller.rb | 2 +- app/helpers/accounts_helper.rb | 9 ++++ app/models/account.rb | 1 + app/views/accounts/edit.html.erb | 6 +++ .../20251126130131_add_account_settings.rb | 5 ++ lib/rails_ext/active_record_has_json.rb | 52 +++++++++++++++++++ test/models/account_test.rb | 14 +++++ 7 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20251126130131_add_account_settings.rb create mode 100644 lib/rails_ext/active_record_has_json.rb diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index be05d66..462ae01 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -17,7 +17,7 @@ class AccountsController < ApplicationController end def account_params - params.require(:account).permit(:name, :logo) + params.require(:account).permit(:name, :logo, settings: {}) end def account_users diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index 559e195..22c6c4a 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -2,4 +2,13 @@ module AccountsHelper def account_logo_tag(style: nil) tag.figure image_tag(fresh_account_logo_path, alt: "Account logo", size: 300), class: "account-logo avatar #{style}" end + + def button_to_toggle_setting(label, setting) + button_to account_path(account: { settings: { setting => !Current.account.settings.send("#{setting}?") } }), + method: :put, + role: "checkbox", aria: { checked: true, labelledby: "#{setting}_account_setting" }, tabindex: 0, + class: "btn" do + tag.span(label, id: "#{setting}_account_setting") + end + end end diff --git a/app/models/account.rb b/app/models/account.rb index 36a7648..71ceb7c 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -2,4 +2,5 @@ class Account < ApplicationRecord include Joinable has_one_attached :logo + has_json settings: { restrict_room_creation_to_administrators: :boolean }, delegate: true end diff --git a/app/views/accounts/edit.html.erb b/app/views/accounts/edit.html.erb index 93a828a..2b6d5ab 100644 --- a/app/views/accounts/edit.html.erb +++ b/app/views/accounts/edit.html.erb @@ -64,6 +64,12 @@ <% end %> <% end %> + + <% if Current.account.restrict_room_creation_to_administrators? %> + <%= button_to_toggle_setting "Allow everyone to create new rooms", :restrict_room_creation_to_administrators %> + <% else %> + <%= button_to_toggle_setting "Only allow admins to create new rooms", :restrict_room_creation_to_administrators %> + <% end %> <% else %> <%= account_logo_tag style: "txt-xx-large center" %>

<%= @account.name %>

diff --git a/db/migrate/20251126130131_add_account_settings.rb b/db/migrate/20251126130131_add_account_settings.rb new file mode 100644 index 0000000..6c97f4e --- /dev/null +++ b/db/migrate/20251126130131_add_account_settings.rb @@ -0,0 +1,5 @@ +class AddAccountSettings < ActiveRecord::Migration[7.2] + def change + add_column :accounts, :settings, :json, default: {} + end +end diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_has_json.rb new file mode 100644 index 0000000..8a3bdd7 --- /dev/null +++ b/lib/rails_ext/active_record_has_json.rb @@ -0,0 +1,52 @@ +module ActiveRecord + class TypedJson + def initialize(schema, data:) + @schema, @data = schema, data + end + + def assign_data_with_type_casting(new_data) + new_data.each do |key, value| + @data[key] = ActiveModel::Type.lookup(@schema[key.to_sym]).cast(value) + end + end + + private + def method_missing(method_name, *args, **kwargs) + key = method_name.to_s.remove(/(\?|=)/) + + if key_type = @schema[key.to_sym] + if method_name.ends_with?("?") + @data[key].present? + elsif method_name.ends_with?("=") + value = args.first + @data[key] = ActiveModel::Type.lookup(key_type).cast(value) + else + @data.fetch(key) + end + else + super + end + end + + def respond_to_missing?(method_name, include_private = false) + @schema.key?(method_name.to_s.remove(/[?=]/).to_sym) || super + end + end + + class Base + class << self + def has_json(delegate: false, **schemas) + schemas.each do |name, schema| + define_method(name) { ActiveRecord::TypedJson.new(schema, data: self[name]) } + define_method("#{name}=") { |data| send(name).assign_data_with_type_casting(data) } + + schema.keys.each do |schema_key| + define_method(schema_key) { send(name).send(schema_key) } + define_method("#{schema_key}?") { send(name).send("#{schema_key}?") } + define_method("#{schema_key}=") { |value| send(name).send("#{schema_key}=", value) } + end if delegate + end + end + end + end +end diff --git a/test/models/account_test.rb b/test/models/account_test.rb index 94d4f83..e4e3673 100644 --- a/test/models/account_test.rb +++ b/test/models/account_test.rb @@ -1,4 +1,18 @@ require "test_helper" class AccountTest < ActiveSupport::TestCase + test "settings" do + accounts(:signal).restrict_room_creation_to_administrators = true + assert accounts(:signal).restrict_room_creation_to_administrators? + assert_equal({ "restrict_room_creation_to_administrators" => true }, accounts(:signal)[:settings]) + + accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "true" }) + assert accounts(:signal).reload.restrict_room_creation_to_administrators? + + accounts(:signal).restrict_room_creation_to_administrators = false + assert_not accounts(:signal).restrict_room_creation_to_administrators? + assert_equal({ "restrict_room_creation_to_administrators" => false }, accounts(:signal)[:settings]) + accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "false" }) + assert_not accounts(:signal).reload.restrict_room_creation_to_administrators? + end end From f56e33e323e2e2f369e17b581ca91e31a9faaa3f Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 27 Nov 2025 17:32:00 +0100 Subject: [PATCH 02/25] Fix fetching missing values --- lib/rails_ext/active_record_has_json.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_has_json.rb index 8a3bdd7..c2c174e 100644 --- a/lib/rails_ext/active_record_has_json.rb +++ b/lib/rails_ext/active_record_has_json.rb @@ -21,7 +21,7 @@ module ActiveRecord value = args.first @data[key] = ActiveModel::Type.lookup(key_type).cast(value) else - @data.fetch(key) + @data[key] end else super From 15db4033bcb6e8d800e1397db9a8032a352e1217 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 27 Nov 2025 17:32:14 +0100 Subject: [PATCH 03/25] Enforce restriction to create new rooms --- app/controllers/rooms/closeds_controller.rb | 1 + app/controllers/rooms/opens_controller.rb | 1 + app/controllers/rooms_controller.rb | 6 ++++++ app/views/users/sidebars/show.html.erb | 6 ++++-- test/controllers/rooms/closeds_controller_test.rb | 10 ++++++++++ test/controllers/rooms/opens_controller_test.rb | 9 +++++++++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/controllers/rooms/closeds_controller.rb b/app/controllers/rooms/closeds_controller.rb index c6aa7f2..6b28738 100644 --- a/app/controllers/rooms/closeds_controller.rb +++ b/app/controllers/rooms/closeds_controller.rb @@ -3,6 +3,7 @@ class Rooms::ClosedsController < RoomsController before_action :ensure_can_administer, only: %i[ update ] before_action :remember_last_room_visited, only: :show before_action :force_room_type, only: %i[ edit update ] + before_action :ensure_eligible_to_create_rooms, only: %i[ new create ] DEFAULT_ROOM_NAME = "New room" diff --git a/app/controllers/rooms/opens_controller.rb b/app/controllers/rooms/opens_controller.rb index 5f40138..00c7155 100644 --- a/app/controllers/rooms/opens_controller.rb +++ b/app/controllers/rooms/opens_controller.rb @@ -3,6 +3,7 @@ class Rooms::OpensController < RoomsController before_action :ensure_can_administer, only: %i[ update ] before_action :remember_last_room_visited, only: :show before_action :force_room_type, only: %i[ edit update ] + before_action :ensure_eligible_to_create_rooms, only: %i[ new create ] DEFAULT_ROOM_NAME = "New room" diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index b2d8142..3b8ac2e 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -31,6 +31,12 @@ class RoomsController < ApplicationController head :forbidden unless Current.user.can_administer?(@room) end + def ensure_eligible_to_create_rooms + if Current.account.restrict_room_creation_to_administrators? && !Current.user.administrator? + head :forbidden + end + end + def find_messages messages = @room.messages.with_creator.with_attachment_details.with_boosts diff --git a/app/views/users/sidebars/show.html.erb b/app/views/users/sidebars/show.html.erb index 35ab9b1..9b9b140 100644 --- a/app/views/users/sidebars/show.html.erb +++ b/app/views/users/sidebars/show.html.erb @@ -36,8 +36,10 @@ <% end %> - <%= link_to new_rooms_open_path, class: "rooms__new-btn btn room align-center gap txt-reversed", aria: { label: "New Chat Room" } do %> - <%= image_tag "add.svg", size: 20, aria: { hidden: "true" }, style: "view-transition-name: new-room" %> + <% if Current.user.administrator? || !Current.account.restrict_room_creation_to_administrators? %> + <%= link_to new_rooms_open_path, class: "rooms__new-btn btn room align-center gap txt-reversed", aria: { label: "New Chat Room" } do %> + <%= image_tag "add.svg", size: 20, aria: { hidden: "true" }, style: "view-transition-name: new-room" %> + <% end %> <% end %> diff --git a/test/controllers/rooms/closeds_controller_test.rb b/test/controllers/rooms/closeds_controller_test.rb index 5172453..a7200f4 100644 --- a/test/controllers/rooms/closeds_controller_test.rb +++ b/test/controllers/rooms/closeds_controller_test.rb @@ -29,6 +29,16 @@ class Rooms::ClosedsControllerTest < ActionDispatch::IntegrationTest assert_redirected_to room_url(Room.last) end + test "create forbidden by non-admin when account restricts creation to admins" do + accounts(:signal).restrict_room_creation_to_administrators = true + accounts(:signal).save! + + sign_in :jz + post rooms_closeds_url, params: { room: { name: "My New Room" }, user_ids: [ users(:david).id, users(:kevin).id, users(:jason).id ] } + assert_response :forbidden + end + + test "update with membership revisions" do assert_difference -> { rooms(:designers).reload.users.count }, -1 do put rooms_closed_url(rooms(:designers)), params: { diff --git a/test/controllers/rooms/opens_controller_test.rb b/test/controllers/rooms/opens_controller_test.rb index 8820008..f06aacb 100644 --- a/test/controllers/rooms/opens_controller_test.rb +++ b/test/controllers/rooms/opens_controller_test.rb @@ -24,6 +24,15 @@ class Rooms::OpensControllerTest < ActionDispatch::IntegrationTest assert_redirected_to room_url(Room.last) end + test "create forbidden by non-admin when account restricts creation to admins" do + accounts(:signal).restrict_room_creation_to_administrators = true + accounts(:signal).save! + + sign_in :jz + post rooms_opens_url, params: { room: { name: "My New Room" } } + assert_response :forbidden + end + test "only admins or creators can update" do sign_in :jz From 8e94a4aa1eb9af2a4f724542c7fddfe2dc61b46c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Nov 2025 12:43:34 +0100 Subject: [PATCH 04/25] Better wording --- app/controllers/rooms/closeds_controller.rb | 2 +- app/controllers/rooms/opens_controller.rb | 2 +- app/controllers/rooms_controller.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/rooms/closeds_controller.rb b/app/controllers/rooms/closeds_controller.rb index 6b28738..0e8bdf7 100644 --- a/app/controllers/rooms/closeds_controller.rb +++ b/app/controllers/rooms/closeds_controller.rb @@ -3,7 +3,7 @@ class Rooms::ClosedsController < RoomsController before_action :ensure_can_administer, only: %i[ update ] before_action :remember_last_room_visited, only: :show before_action :force_room_type, only: %i[ edit update ] - before_action :ensure_eligible_to_create_rooms, only: %i[ new create ] + before_action :ensure_permission_to_create_rooms, only: %i[ new create ] DEFAULT_ROOM_NAME = "New room" diff --git a/app/controllers/rooms/opens_controller.rb b/app/controllers/rooms/opens_controller.rb index 00c7155..c2bda9e 100644 --- a/app/controllers/rooms/opens_controller.rb +++ b/app/controllers/rooms/opens_controller.rb @@ -3,7 +3,7 @@ class Rooms::OpensController < RoomsController before_action :ensure_can_administer, only: %i[ update ] before_action :remember_last_room_visited, only: :show before_action :force_room_type, only: %i[ edit update ] - before_action :ensure_eligible_to_create_rooms, only: %i[ new create ] + before_action :ensure_permission_to_create_rooms, only: %i[ new create ] DEFAULT_ROOM_NAME = "New room" diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index 3b8ac2e..5de20f2 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -31,7 +31,7 @@ class RoomsController < ApplicationController head :forbidden unless Current.user.can_administer?(@room) end - def ensure_eligible_to_create_rooms + def ensure_permission_to_create_rooms if Current.account.restrict_room_creation_to_administrators? && !Current.user.administrator? head :forbidden end From 32be03a2408b0adc88c7ae4a966108c24d8c64be Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Nov 2025 05:53:40 -0800 Subject: [PATCH 05/25] Rely on method missing --- lib/rails_ext/active_record_has_json.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_has_json.rb index c2c174e..6c446d9 100644 --- a/lib/rails_ext/active_record_has_json.rb +++ b/lib/rails_ext/active_record_has_json.rb @@ -5,9 +5,7 @@ module ActiveRecord end def assign_data_with_type_casting(new_data) - new_data.each do |key, value| - @data[key] = ActiveModel::Type.lookup(@schema[key.to_sym]).cast(value) - end + new_data.each { |key, value| self.send("#{key}=", value) } end private From d3b6507ce2ca26cb48fcc820701866f90905e884 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Nov 2025 15:48:46 -0800 Subject: [PATCH 06/25] Layer on top a more pleasant API for the default case --- app/models/account.rb | 2 +- lib/rails_ext/active_record_has_json.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/account.rb b/app/models/account.rb index 71ceb7c..80e9eb8 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -2,5 +2,5 @@ class Account < ApplicationRecord include Joinable has_one_attached :logo - has_json settings: { restrict_room_creation_to_administrators: :boolean }, delegate: true + has_settings restrict_room_creation_to_administrators: :boolean, max_invites: 10, name: "hero" end diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_has_json.rb index 6c446d9..b8efd0a 100644 --- a/lib/rails_ext/active_record_has_json.rb +++ b/lib/rails_ext/active_record_has_json.rb @@ -45,6 +45,10 @@ module ActiveRecord end if delegate end end + + def has_settings(schema) + has_json settings: schema, delegate: true + end end end end From 593f8dd04c7800ecab9dda055f5db1e616605693 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Nov 2025 15:49:10 -0800 Subject: [PATCH 07/25] No need for self --- lib/rails_ext/active_record_has_json.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_has_json.rb index b8efd0a..7a5540b 100644 --- a/lib/rails_ext/active_record_has_json.rb +++ b/lib/rails_ext/active_record_has_json.rb @@ -5,7 +5,7 @@ module ActiveRecord end def assign_data_with_type_casting(new_data) - new_data.each { |key, value| self.send("#{key}=", value) } + new_data.each { |key, value| send("#{key}=", value) } end private From f7c3aaa2a9e349be43e85de3f9ccb85290b1afe3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Nov 2025 16:35:25 -0800 Subject: [PATCH 08/25] Allow for default values --- app/models/account.rb | 2 +- lib/rails_ext/active_record_has_json.rb | 35 +++++++++++++++++++++---- test/models/account_test.rb | 5 ++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/models/account.rb b/app/models/account.rb index 80e9eb8..2da750c 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -2,5 +2,5 @@ class Account < ApplicationRecord include Joinable has_one_attached :logo - has_settings restrict_room_creation_to_administrators: :boolean, max_invites: 10, name: "hero" + has_settings restrict_room_creation_to_administrators: false end diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_has_json.rb index 7a5540b..01245cf 100644 --- a/lib/rails_ext/active_record_has_json.rb +++ b/lib/rails_ext/active_record_has_json.rb @@ -1,23 +1,24 @@ module ActiveRecord - class TypedJson + class SchematizedJson def initialize(schema, data:) @schema, @data = schema, data + update_data_with_schema_defaults end def assign_data_with_type_casting(new_data) - new_data.each { |key, value| send("#{key}=", value) } + new_data.each { |key, value| send "#{key}=", value } end private def method_missing(method_name, *args, **kwargs) key = method_name.to_s.remove(/(\?|=)/) - if key_type = @schema[key.to_sym] + if @schema.key? key.to_sym if method_name.ends_with?("?") @data[key].present? elsif method_name.ends_with?("=") value = args.first - @data[key] = ActiveModel::Type.lookup(key_type).cast(value) + @data[key] = lookup_schema_type_for(key).cast(value) else @data[key] end @@ -29,13 +30,34 @@ module ActiveRecord def respond_to_missing?(method_name, include_private = false) @schema.key?(method_name.to_s.remove(/[?=]/).to_sym) || super end + + def lookup_schema_type_for(key) + type_or_default_value = @schema[key.to_sym] + + case type_or_default_value + when :boolean, :integer, :string + ActiveModel::Type.lookup(type_or_default_value) + when TrueClass, FalseClass + ActiveModel::Type.lookup(:boolean) + when Integer + ActiveModel::Type.lookup(:integer) + when String + ActiveModel::Type.lookup(:string) + else + raise "Only boolean, integer, or strings are allowed as JSON schema types" + end + end + + def update_data_with_schema_defaults + @data.reverse_merge!(@schema.to_h { |k, v| [ k.to_s, v.is_a?(Symbol) ? nil : v ] }) + end end class Base class << self def has_json(delegate: false, **schemas) schemas.each do |name, schema| - define_method(name) { ActiveRecord::TypedJson.new(schema, data: self[name]) } + define_method(name) { ActiveRecord::SchematizedJson.new(schema, data: self[name]) } define_method("#{name}=") { |data| send(name).assign_data_with_type_casting(data) } schema.keys.each do |schema_key| @@ -43,6 +65,9 @@ module ActiveRecord define_method("#{schema_key}?") { send(name).send("#{schema_key}?") } define_method("#{schema_key}=") { |value| send(name).send("#{schema_key}=", value) } end if delegate + + # Ensures default values are set before saving + before_save -> { send(name) } end end diff --git a/test/models/account_test.rb b/test/models/account_test.rb index e4e3673..fd6be9a 100644 --- a/test/models/account_test.rb +++ b/test/models/account_test.rb @@ -15,4 +15,9 @@ class AccountTest < ActiveSupport::TestCase accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "false" }) assert_not accounts(:signal).reload.restrict_room_creation_to_administrators? end + + test "default settings" do + a = Account.create! name: "New account" + assert_equal({ "restrict_room_creation_to_administrators" => false }, a[:settings]) + end end From 6476bab4ccdcfa5259baf88edda1952bc844ad9e Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Nov 2025 16:36:24 -0800 Subject: [PATCH 09/25] Use consistent yield naming --- lib/rails_ext/active_record_has_json.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_has_json.rb index 01245cf..a66d388 100644 --- a/lib/rails_ext/active_record_has_json.rb +++ b/lib/rails_ext/active_record_has_json.rb @@ -6,7 +6,7 @@ module ActiveRecord end def assign_data_with_type_casting(new_data) - new_data.each { |key, value| send "#{key}=", value } + new_data.each { |k, v| send "#{k}=", v } end private From 66b4e41281b398135e45f31156bed5f5bc65bf57 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 28 Nov 2025 16:38:39 -0800 Subject: [PATCH 10/25] Rename --- ...ctive_record_has_json.rb => active_record_schematized_json.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/rails_ext/{active_record_has_json.rb => active_record_schematized_json.rb} (100%) diff --git a/lib/rails_ext/active_record_has_json.rb b/lib/rails_ext/active_record_schematized_json.rb similarity index 100% rename from lib/rails_ext/active_record_has_json.rb rename to lib/rails_ext/active_record_schematized_json.rb From 53671b48e07ef8a2c9804e2abd535661c4ec76ac Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 29 Nov 2025 08:52:29 -0800 Subject: [PATCH 11/25] Update to latest Rails 8.2.0 alpha --- Gemfile | 6 ++- Gemfile.lock | 103 ++++++++++++++++++++++++++------------------------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/Gemfile b/Gemfile index 1532614..5de9f88 100644 --- a/Gemfile +++ b/Gemfile @@ -4,10 +4,12 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Rails gem "rails", github: "rails/rails", branch: "main" +gem "ostruct" +gem "benchmark" # Drivers -gem "sqlite3", "~> 2.7" -gem "redis", "~> 5.4" +gem "sqlite3" +gem "redis", "~> 4.0" # Deployment gem "puma", "~> 6.6" diff --git a/Gemfile.lock b/Gemfile.lock index 30b671e..1fe1464 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,8 @@ GIT remote: https://github.com/hotwired/turbo-rails.git - revision: 30cd8fcc6f82c1ad4edd1ed6069ba878f21f02b3 + revision: 0eb3c0387401de5f14b0c9d18be640ffbad45ad0 specs: - turbo-rails (2.0.16) + turbo-rails (2.0.20) actionpack (>= 7.1.0) railties (>= 7.1.0) @@ -17,16 +17,16 @@ GIT GIT remote: https://github.com/rails/propshaft.git - revision: e49a9de659ff27462015e54dd832e86e762a6ddc + revision: 68bb8c5758b587d5184e6d13173cb057beec8d3c specs: - propshaft (1.2.1) + propshaft (1.3.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack GIT remote: https://github.com/rails/rails.git - revision: 1a02651ac37fb64b4de2a2b73461d86acf9c98fb + revision: 690ec8898318b8f50714e86676353ebe1551261e branch: main specs: actioncable (8.2.0.alpha) @@ -130,11 +130,12 @@ GEM specs: action_text-trix (2.1.15) railties - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) base64 (0.3.0) bcrypt (3.1.20) + benchmark (0.5.0) bigdecimal (3.3.1) brakeman (7.1.1) racc @@ -151,7 +152,7 @@ GEM chunky_png (1.4.0) concurrent-ruby (1.3.5) connection_pool (2.5.5) - crack (1.0.0) + crack (1.0.1) bigdecimal rexml crass (1.0.6) @@ -173,7 +174,7 @@ GEM addressable (>= 2.5.0) globalid (1.3.0) activesupport (>= 6.1) - hashdiff (1.2.0) + hashdiff (1.2.1) i18n (1.14.7) concurrent-ruby (~> 1.0) image_processing (1.14.0) @@ -187,7 +188,7 @@ GEM jbuilder (2.14.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) - json (2.13.2) + json (2.16.0) jwt (3.1.2) base64 kredis (1.8.0) @@ -212,7 +213,7 @@ GEM logger mini_mime (1.1.5) minitest (5.26.2) - mocha (2.7.1) + mocha (2.8.2) ruby2_keywords (>= 0.0.5) mono_logger (1.1.2) multi_json (1.17.0) @@ -238,9 +239,10 @@ GEM racc (~> 1.4) nokogiri (1.18.10-x86_64-linux-gnu) racc (~> 1.4) - openssl (3.3.0) + openssl (3.3.2) + ostruct (0.6.3) parallel (1.27.0) - parser (3.3.9.0) + parser (3.3.10.0) ast (~> 2.4.1) racc platform_agent (1.0.1) @@ -249,16 +251,16 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.4.0) + prism (1.6.0) psych (5.2.6) date stringio - public_suffix (6.0.2) + public_suffix (7.0.0) puma (6.6.1) nio4r (~> 2.0) racc (1.8.1) rack (3.2.4) - rack-protection (4.1.1) + rack-protection (4.2.1) base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) @@ -286,13 +288,10 @@ GEM erb psych (>= 4.0.0) tsort - redis (5.4.1) - redis-client (>= 0.22.0) - redis-client (0.25.2) - connection_pool + redis (4.8.1) redis-namespace (1.11.0) redis (>= 4) - regexp_parser (2.11.2) + regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) resque (2.7.0) @@ -303,12 +302,12 @@ GEM resque-pool (0.7.1) rake (>= 10.0, < 14.0) resque (>= 1.22, < 3) - rexml (3.4.1) - rqrcode (3.1.0) + rexml (3.4.4) + rqrcode (3.1.1) chunky_png (~> 1.0) rqrcode_core (~> 2.0) - rqrcode_core (2.0.0) - rubocop (1.80.0) + rqrcode_core (2.0.1) + rubocop (1.81.7) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -316,17 +315,17 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.46.0) + rubocop-ast (1.48.0) parser (>= 3.3.7.2) prism (~> 1.4) - rubocop-performance (1.25.0) + rubocop-performance (1.26.1) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rails (2.33.3) + rubocop-ast (>= 1.47.1, < 2.0) + rubocop-rails (2.34.2) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -341,53 +340,53 @@ GEM ffi (~> 1.12) logger ruby2_keywords (0.0.5) - rubyzip (3.0.2) + rubyzip (3.2.2) securerandom (0.4.1) - selenium-webdriver (4.35.0) + selenium-webdriver (4.38.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) - sentry-rails (5.26.0) - railties (>= 5.0) - sentry-ruby (~> 5.26.0) - sentry-ruby (5.26.0) + sentry-rails (6.2.0) + railties (>= 5.2.0) + sentry-ruby (~> 6.2.0) + sentry-ruby (6.2.0) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - sinatra (4.1.1) + sinatra (4.2.1) logger (>= 1.6.0) mustermann (~> 3.0) rack (>= 3.0.0, < 4) - rack-protection (= 4.1.1) + rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - sqlite3 (2.7.3-aarch64-linux-gnu) - sqlite3 (2.7.3-arm64-darwin) - sqlite3 (2.7.3-x86_64-darwin) - sqlite3 (2.7.3-x86_64-linux-gnu) + sqlite3 (2.8.1-aarch64-linux-gnu) + sqlite3 (2.8.1-arm64-darwin) + sqlite3 (2.8.1-x86_64-darwin) + sqlite3 (2.8.1-x86_64-linux-gnu) stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.1.8) thor (1.4.0) - thruster (0.1.15-aarch64-linux) - thruster (0.1.15-arm64-darwin) - thruster (0.1.15-x86_64-darwin) - thruster (0.1.15-x86_64-linux) + thruster (0.1.16-aarch64-linux) + thruster (0.1.16-arm64-darwin) + thruster (0.1.16-x86_64-darwin) + thruster (0.1.16-x86_64-linux) tilt (2.6.1) timeout (0.4.4) tsort (0.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (3.1.5) - unicode-emoji (~> 4.0, >= 4.0.4) - unicode-emoji (4.0.4) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) uri (1.1.1) useragent (0.16.11) web-push (3.0.2) jwt (~> 3.0) openssl (~> 3.0) - webmock (3.25.1) + webmock (3.26.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -408,6 +407,7 @@ PLATFORMS DEPENDENCIES bcrypt + benchmark brakeman capybara debug @@ -419,6 +419,7 @@ DEPENDENCIES kredis mocha net-http-persistent + ostruct platform_agent propshaft! puma (~> 6.6) @@ -432,7 +433,7 @@ DEPENDENCIES selenium-webdriver sentry-rails sentry-ruby - sqlite3 (~> 2.7) + sqlite3 stimulus-rails thruster turbo-rails! From d323c3cfc0f87cef300310f5902f41aea83a65f4 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 29 Nov 2025 08:55:51 -0800 Subject: [PATCH 12/25] Now required to be explicitly included Not sure why --- test/test_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_helper.rb b/test/test_helper.rb index 33a20be..9c1310c 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -5,6 +5,7 @@ require "rails/test_help" require "minitest/unit" require "mocha/minitest" require "webmock/minitest" +require "turbo/broadcastable/test_helper" WebMock.enable! From 20ba1cf2aeef0e60d8eee24bdf9e17846aa1deaf Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 29 Nov 2025 08:58:48 -0800 Subject: [PATCH 13/25] Ensure mutable string is used to prevent warning --- app/models/webhook.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/webhook.rb b/app/models/webhook.rb index 1afefa6..d8e7b43 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -53,7 +53,7 @@ class Webhook < ApplicationRecord end def extract_text_from(response) - response.body.dup.force_encoding("UTF-8") if response.code == "200" && response.content_type.in?(%w[ text/html text/plain ]) + String.new(response.body).force_encoding("UTF-8") if response.code == "200" && response.content_type.in?(%w[ text/html text/plain ]) end def receive_text_reply_to(room, text:) From 6c59b8c82b0a18fff93cc64cb4a6af44084131e0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 29 Nov 2025 09:04:08 -0800 Subject: [PATCH 14/25] Use public_send instead of send --- lib/rails_ext/active_record_schematized_json.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/rails_ext/active_record_schematized_json.rb b/lib/rails_ext/active_record_schematized_json.rb index a66d388..a5380ab 100644 --- a/lib/rails_ext/active_record_schematized_json.rb +++ b/lib/rails_ext/active_record_schematized_json.rb @@ -6,7 +6,7 @@ module ActiveRecord end def assign_data_with_type_casting(new_data) - new_data.each { |k, v| send "#{k}=", v } + new_data.each { |k, v| public_send "#{k}=", v } end private @@ -58,12 +58,12 @@ module ActiveRecord def has_json(delegate: false, **schemas) schemas.each do |name, schema| define_method(name) { ActiveRecord::SchematizedJson.new(schema, data: self[name]) } - define_method("#{name}=") { |data| send(name).assign_data_with_type_casting(data) } + define_method("#{name}=") { |data| public_send(name).assign_data_with_type_casting(data) } schema.keys.each do |schema_key| - define_method(schema_key) { send(name).send(schema_key) } - define_method("#{schema_key}?") { send(name).send("#{schema_key}?") } - define_method("#{schema_key}=") { |value| send(name).send("#{schema_key}=", value) } + define_method(schema_key) { public_send(name).public_send(schema_key) } + define_method("#{schema_key}?") { public_send(name).public_send("#{schema_key}?") } + define_method("#{schema_key}=") { |value| send(name).public_send("#{schema_key}=", value) } end if delegate # Ensures default values are set before saving From 42c411b66064163a92703e30b91ec8dbb462437c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 29 Nov 2025 10:38:25 -0800 Subject: [PATCH 15/25] Use upstream version of has_json --- Gemfile | 2 +- Gemfile.lock | 4 +- app/models/account.rb | 2 +- .../active_record_schematized_json.rb | 79 ------------------- 4 files changed, 4 insertions(+), 83 deletions(-) delete mode 100644 lib/rails_ext/active_record_schematized_json.rb diff --git a/Gemfile b/Gemfile index 5de9f88..f63b7ff 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Rails -gem "rails", github: "rails/rails", branch: "main" +gem "rails", github: "rails/rails", ref: "7727f43d38bf1783550e7a47207a9548e34a2c25" # branch: "main" gem "ostruct" gem "benchmark" diff --git a/Gemfile.lock b/Gemfile.lock index 1fe1464..d4ea7f8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,8 +26,8 @@ GIT GIT remote: https://github.com/rails/rails.git - revision: 690ec8898318b8f50714e86676353ebe1551261e - branch: main + revision: 7727f43d38bf1783550e7a47207a9548e34a2c25 + ref: 7727f43d38bf1783550e7a47207a9548e34a2c25 specs: actioncable (8.2.0.alpha) actionpack (= 8.2.0.alpha) diff --git a/app/models/account.rb b/app/models/account.rb index 2da750c..239c9fc 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -2,5 +2,5 @@ class Account < ApplicationRecord include Joinable has_one_attached :logo - has_settings restrict_room_creation_to_administrators: false + has_json :settings, schema: { restrict_room_creation_to_administrators: false }, delegate: true end diff --git a/lib/rails_ext/active_record_schematized_json.rb b/lib/rails_ext/active_record_schematized_json.rb deleted file mode 100644 index a5380ab..0000000 --- a/lib/rails_ext/active_record_schematized_json.rb +++ /dev/null @@ -1,79 +0,0 @@ -module ActiveRecord - class SchematizedJson - def initialize(schema, data:) - @schema, @data = schema, data - update_data_with_schema_defaults - end - - def assign_data_with_type_casting(new_data) - new_data.each { |k, v| public_send "#{k}=", v } - end - - private - def method_missing(method_name, *args, **kwargs) - key = method_name.to_s.remove(/(\?|=)/) - - if @schema.key? key.to_sym - if method_name.ends_with?("?") - @data[key].present? - elsif method_name.ends_with?("=") - value = args.first - @data[key] = lookup_schema_type_for(key).cast(value) - else - @data[key] - end - else - super - end - end - - def respond_to_missing?(method_name, include_private = false) - @schema.key?(method_name.to_s.remove(/[?=]/).to_sym) || super - end - - def lookup_schema_type_for(key) - type_or_default_value = @schema[key.to_sym] - - case type_or_default_value - when :boolean, :integer, :string - ActiveModel::Type.lookup(type_or_default_value) - when TrueClass, FalseClass - ActiveModel::Type.lookup(:boolean) - when Integer - ActiveModel::Type.lookup(:integer) - when String - ActiveModel::Type.lookup(:string) - else - raise "Only boolean, integer, or strings are allowed as JSON schema types" - end - end - - def update_data_with_schema_defaults - @data.reverse_merge!(@schema.to_h { |k, v| [ k.to_s, v.is_a?(Symbol) ? nil : v ] }) - end - end - - class Base - class << self - def has_json(delegate: false, **schemas) - schemas.each do |name, schema| - define_method(name) { ActiveRecord::SchematizedJson.new(schema, data: self[name]) } - define_method("#{name}=") { |data| public_send(name).assign_data_with_type_casting(data) } - - schema.keys.each do |schema_key| - define_method(schema_key) { public_send(name).public_send(schema_key) } - define_method("#{schema_key}?") { public_send(name).public_send("#{schema_key}?") } - define_method("#{schema_key}=") { |value| send(name).public_send("#{schema_key}=", value) } - end if delegate - - # Ensures default values are set before saving - before_save -> { send(name) } - end - end - - def has_settings(schema) - has_json settings: schema, delegate: true - end - end - end -end From 559629537b2fb93a6d27a0bf22d115de68dacff3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 29 Nov 2025 10:52:01 -0800 Subject: [PATCH 16/25] We don't need to specify the default any more --- db/migrate/20251126130131_add_account_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20251126130131_add_account_settings.rb b/db/migrate/20251126130131_add_account_settings.rb index 6c97f4e..375f53d 100644 --- a/db/migrate/20251126130131_add_account_settings.rb +++ b/db/migrate/20251126130131_add_account_settings.rb @@ -1,5 +1,5 @@ class AddAccountSettings < ActiveRecord::Migration[7.2] def change - add_column :accounts, :settings, :json, default: {} + add_column :accounts, :settings, :json end end From 796195c2cc26d4eeb80926d3100cc07c79a3c750 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 29 Nov 2025 11:09:23 -0800 Subject: [PATCH 17/25] Give up on the auto delegation to get a cleaner API --- Gemfile | 2 +- Gemfile.lock | 4 ++-- app/models/account.rb | 2 +- app/views/accounts/edit.html.erb | 2 +- app/views/users/sidebars/show.html.erb | 2 +- test/models/account_test.rb | 12 ++++++------ 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index f63b7ff..b910e56 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Rails -gem "rails", github: "rails/rails", ref: "7727f43d38bf1783550e7a47207a9548e34a2c25" # branch: "main" +gem "rails", github: "rails/rails", ref: "478abdb62b4ff769ae2d9cf2e34be8335d9b6f7e" # branch: "main" gem "ostruct" gem "benchmark" diff --git a/Gemfile.lock b/Gemfile.lock index d4ea7f8..56e5adb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,8 +26,8 @@ GIT GIT remote: https://github.com/rails/rails.git - revision: 7727f43d38bf1783550e7a47207a9548e34a2c25 - ref: 7727f43d38bf1783550e7a47207a9548e34a2c25 + revision: 478abdb62b4ff769ae2d9cf2e34be8335d9b6f7e + ref: 478abdb62b4ff769ae2d9cf2e34be8335d9b6f7e specs: actioncable (8.2.0.alpha) actionpack (= 8.2.0.alpha) diff --git a/app/models/account.rb b/app/models/account.rb index 239c9fc..f0b7c98 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -2,5 +2,5 @@ class Account < ApplicationRecord include Joinable has_one_attached :logo - has_json :settings, schema: { restrict_room_creation_to_administrators: false }, delegate: true + has_json :settings, restrict_room_creation_to_administrators: false end diff --git a/app/views/accounts/edit.html.erb b/app/views/accounts/edit.html.erb index 2b6d5ab..d4a4ab8 100644 --- a/app/views/accounts/edit.html.erb +++ b/app/views/accounts/edit.html.erb @@ -65,7 +65,7 @@ <% end %> - <% if Current.account.restrict_room_creation_to_administrators? %> + <% if Current.account.settings.restrict_room_creation_to_administrators? %> <%= button_to_toggle_setting "Allow everyone to create new rooms", :restrict_room_creation_to_administrators %> <% else %> <%= button_to_toggle_setting "Only allow admins to create new rooms", :restrict_room_creation_to_administrators %> diff --git a/app/views/users/sidebars/show.html.erb b/app/views/users/sidebars/show.html.erb index 9b9b140..709c310 100644 --- a/app/views/users/sidebars/show.html.erb +++ b/app/views/users/sidebars/show.html.erb @@ -36,7 +36,7 @@ <% end %> - <% if Current.user.administrator? || !Current.account.restrict_room_creation_to_administrators? %> + <% if Current.user.administrator? || !Current.account.settings.restrict_room_creation_to_administrators? %> <%= link_to new_rooms_open_path, class: "rooms__new-btn btn room align-center gap txt-reversed", aria: { label: "New Chat Room" } do %> <%= image_tag "add.svg", size: 20, aria: { hidden: "true" }, style: "view-transition-name: new-room" %> <% end %> diff --git a/test/models/account_test.rb b/test/models/account_test.rb index fd6be9a..75fff6c 100644 --- a/test/models/account_test.rb +++ b/test/models/account_test.rb @@ -2,18 +2,18 @@ require "test_helper" class AccountTest < ActiveSupport::TestCase test "settings" do - accounts(:signal).restrict_room_creation_to_administrators = true - assert accounts(:signal).restrict_room_creation_to_administrators? + accounts(:signal).settings.restrict_room_creation_to_administrators = true + assert accounts(:signal).settings.restrict_room_creation_to_administrators? assert_equal({ "restrict_room_creation_to_administrators" => true }, accounts(:signal)[:settings]) accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "true" }) - assert accounts(:signal).reload.restrict_room_creation_to_administrators? + assert accounts(:signal).reload.settings.restrict_room_creation_to_administrators? - accounts(:signal).restrict_room_creation_to_administrators = false - assert_not accounts(:signal).restrict_room_creation_to_administrators? + accounts(:signal).settings.restrict_room_creation_to_administrators = false + assert_not accounts(:signal).settings.restrict_room_creation_to_administrators? assert_equal({ "restrict_room_creation_to_administrators" => false }, accounts(:signal)[:settings]) accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "false" }) - assert_not accounts(:signal).reload.restrict_room_creation_to_administrators? + assert_not accounts(:signal).reload.settings.restrict_room_creation_to_administrators? end test "default settings" do From e8626f9d5d03a4012c5fddca388d54ac7bd17b3d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 30 Nov 2025 21:07:26 +0100 Subject: [PATCH 18/25] Use rails edge that now includes the feature --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index b910e56..5de9f88 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Rails -gem "rails", github: "rails/rails", ref: "478abdb62b4ff769ae2d9cf2e34be8335d9b6f7e" # branch: "main" +gem "rails", github: "rails/rails", branch: "main" gem "ostruct" gem "benchmark" diff --git a/Gemfile.lock b/Gemfile.lock index 56e5adb..36b0fa9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,8 +26,8 @@ GIT GIT remote: https://github.com/rails/rails.git - revision: 478abdb62b4ff769ae2d9cf2e34be8335d9b6f7e - ref: 478abdb62b4ff769ae2d9cf2e34be8335d9b6f7e + revision: 83b33ca4809bf570d9a15c12f626eae2181583be + branch: main specs: actioncable (8.2.0.alpha) actionpack (= 8.2.0.alpha) @@ -441,4 +441,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.5.9 + 2.7.2 From bd3b0c5988fd599baeacb7e8a617634c147e2372 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 30 Nov 2025 21:07:34 +0100 Subject: [PATCH 19/25] Not needed --- test/models/account_test.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/models/account_test.rb b/test/models/account_test.rb index 75fff6c..2585ba0 100644 --- a/test/models/account_test.rb +++ b/test/models/account_test.rb @@ -15,9 +15,4 @@ class AccountTest < ActiveSupport::TestCase accounts(:signal).update!(settings: { "restrict_room_creation_to_administrators" => "false" }) assert_not accounts(:signal).reload.settings.restrict_room_creation_to_administrators? end - - test "default settings" do - a = Account.create! name: "New account" - assert_equal({ "restrict_room_creation_to_administrators" => false }, a[:settings]) - end end From 5266ffc0494c88ffe367e4e45a212207cef2355a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 30 Nov 2025 21:07:46 +0100 Subject: [PATCH 20/25] Always just go through the settings object --- app/controllers/rooms_controller.rb | 2 +- test/controllers/rooms/closeds_controller_test.rb | 2 +- test/controllers/rooms/opens_controller_test.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index 5de20f2..2c308c8 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -32,7 +32,7 @@ class RoomsController < ApplicationController end def ensure_permission_to_create_rooms - if Current.account.restrict_room_creation_to_administrators? && !Current.user.administrator? + if Current.account.settings.restrict_room_creation_to_administrators? && !Current.user.administrator? head :forbidden end end diff --git a/test/controllers/rooms/closeds_controller_test.rb b/test/controllers/rooms/closeds_controller_test.rb index a7200f4..d8976ac 100644 --- a/test/controllers/rooms/closeds_controller_test.rb +++ b/test/controllers/rooms/closeds_controller_test.rb @@ -30,7 +30,7 @@ class Rooms::ClosedsControllerTest < ActionDispatch::IntegrationTest end test "create forbidden by non-admin when account restricts creation to admins" do - accounts(:signal).restrict_room_creation_to_administrators = true + accounts(:signal).settings.restrict_room_creation_to_administrators = true accounts(:signal).save! sign_in :jz diff --git a/test/controllers/rooms/opens_controller_test.rb b/test/controllers/rooms/opens_controller_test.rb index f06aacb..0f91294 100644 --- a/test/controllers/rooms/opens_controller_test.rb +++ b/test/controllers/rooms/opens_controller_test.rb @@ -25,7 +25,7 @@ class Rooms::OpensControllerTest < ActionDispatch::IntegrationTest end test "create forbidden by non-admin when account restricts creation to admins" do - accounts(:signal).restrict_room_creation_to_administrators = true + accounts(:signal).settings.restrict_room_creation_to_administrators = true accounts(:signal).save! sign_in :jz From b1325ccee748931aaa40aca6ac4f4a5beeee5c4d Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Mon, 1 Dec 2025 15:31:07 +0100 Subject: [PATCH 21/25] Bump Redis --- Gemfile | 2 +- Gemfile.lock | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 5de9f88..305d3b1 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ gem "benchmark" # Drivers gem "sqlite3" -gem "redis", "~> 4.0" +gem "redis", "~> 5.0" # Deployment gem "puma", "~> 6.6" diff --git a/Gemfile.lock b/Gemfile.lock index 36b0fa9..9a054c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -288,7 +288,10 @@ GEM erb psych (>= 4.0.0) tsort - redis (4.8.1) + redis (5.4.1) + redis-client (>= 0.22.0) + redis-client (0.26.1) + connection_pool redis-namespace (1.11.0) redis (>= 4) regexp_parser (2.11.3) @@ -425,7 +428,7 @@ DEPENDENCIES puma (~> 6.6) rails! rails_autolink - redis (~> 5.4) + redis (~> 5.0) resque (~> 2.7.0) resque-pool (~> 0.7.1) rqrcode From 71b5edae01e78b718a1c137ce226653edd46e5ae Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Mon, 1 Dec 2025 15:31:53 +0100 Subject: [PATCH 22/25] Run migrations --- db/schema.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 07e0f23..658630d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,12 +10,13 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_11_26_115722) do +ActiveRecord::Schema[8.2].define(version: 2025_11_26_130131) do create_table "accounts", force: :cascade do |t| t.datetime "created_at", null: false t.text "custom_styles" t.string "join_code", null: false t.string "name", null: false + t.json "settings" t.datetime "updated_at", null: false end From aec8747710c83660fb3cb10ff8f8a7e2f893dd67 Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Mon, 1 Dec 2025 16:34:51 +0100 Subject: [PATCH 23/25] Fix failing system tests Something broke when the dependencies were updated so I copied over the Gemfile.lock file from main - which is known to work --- Gemfile | 2 +- Gemfile.lock | 98 ++++++++++++++++++++++++++-------------------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Gemfile b/Gemfile index 305d3b1..6820e1d 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ gem "benchmark" # Drivers gem "sqlite3" -gem "redis", "~> 5.0" +gem "redis", "~> 5.4" # Deployment gem "puma", "~> 6.6" diff --git a/Gemfile.lock b/Gemfile.lock index 9a054c5..75a617f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,8 @@ GIT remote: https://github.com/hotwired/turbo-rails.git - revision: 0eb3c0387401de5f14b0c9d18be640ffbad45ad0 + revision: 30cd8fcc6f82c1ad4edd1ed6069ba878f21f02b3 specs: - turbo-rails (2.0.20) + turbo-rails (2.0.16) actionpack (>= 7.1.0) railties (>= 7.1.0) @@ -17,16 +17,16 @@ GIT GIT remote: https://github.com/rails/propshaft.git - revision: 68bb8c5758b587d5184e6d13173cb057beec8d3c + revision: e49a9de659ff27462015e54dd832e86e762a6ddc specs: - propshaft (1.3.1) + propshaft (1.2.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack GIT remote: https://github.com/rails/rails.git - revision: 83b33ca4809bf570d9a15c12f626eae2181583be + revision: 1a02651ac37fb64b4de2a2b73461d86acf9c98fb branch: main specs: actioncable (8.2.0.alpha) @@ -130,8 +130,8 @@ GEM specs: action_text-trix (2.1.15) railties - addressable (2.8.8) - public_suffix (>= 2.0.2, < 8.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) ast (2.4.3) base64 (0.3.0) bcrypt (3.1.20) @@ -152,7 +152,7 @@ GEM chunky_png (1.4.0) concurrent-ruby (1.3.5) connection_pool (2.5.5) - crack (1.0.1) + crack (1.0.0) bigdecimal rexml crass (1.0.6) @@ -174,7 +174,7 @@ GEM addressable (>= 2.5.0) globalid (1.3.0) activesupport (>= 6.1) - hashdiff (1.2.1) + hashdiff (1.2.0) i18n (1.14.7) concurrent-ruby (~> 1.0) image_processing (1.14.0) @@ -188,7 +188,7 @@ GEM jbuilder (2.14.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) - json (2.16.0) + json (2.13.2) jwt (3.1.2) base64 kredis (1.8.0) @@ -213,7 +213,7 @@ GEM logger mini_mime (1.1.5) minitest (5.26.2) - mocha (2.8.2) + mocha (2.7.1) ruby2_keywords (>= 0.0.5) mono_logger (1.1.2) multi_json (1.17.0) @@ -239,10 +239,10 @@ GEM racc (~> 1.4) nokogiri (1.18.10-x86_64-linux-gnu) racc (~> 1.4) - openssl (3.3.2) + openssl (3.3.0) ostruct (0.6.3) parallel (1.27.0) - parser (3.3.10.0) + parser (3.3.9.0) ast (~> 2.4.1) racc platform_agent (1.0.1) @@ -251,16 +251,16 @@ GEM pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.6.0) + prism (1.4.0) psych (5.2.6) date stringio - public_suffix (7.0.0) + public_suffix (6.0.2) puma (6.6.1) nio4r (~> 2.0) racc (1.8.1) rack (3.2.4) - rack-protection (4.2.1) + rack-protection (4.1.1) base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) @@ -290,11 +290,11 @@ GEM tsort redis (5.4.1) redis-client (>= 0.22.0) - redis-client (0.26.1) + redis-client (0.25.2) connection_pool redis-namespace (1.11.0) redis (>= 4) - regexp_parser (2.11.3) + regexp_parser (2.11.2) reline (0.6.3) io-console (~> 0.5) resque (2.7.0) @@ -305,12 +305,12 @@ GEM resque-pool (0.7.1) rake (>= 10.0, < 14.0) resque (>= 1.22, < 3) - rexml (3.4.4) - rqrcode (3.1.1) + rexml (3.4.1) + rqrcode (3.1.0) chunky_png (~> 1.0) rqrcode_core (~> 2.0) - rqrcode_core (2.0.1) - rubocop (1.81.7) + rqrcode_core (2.0.0) + rubocop (1.80.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -318,17 +318,17 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.47.1, < 2.0) + rubocop-ast (>= 1.46.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.48.0) + rubocop-ast (1.46.0) parser (>= 3.3.7.2) prism (~> 1.4) - rubocop-performance (1.26.1) + rubocop-performance (1.25.0) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) - rubocop-ast (>= 1.47.1, < 2.0) - rubocop-rails (2.34.2) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rails (2.33.3) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -343,53 +343,53 @@ GEM ffi (~> 1.12) logger ruby2_keywords (0.0.5) - rubyzip (3.2.2) + rubyzip (3.0.2) securerandom (0.4.1) - selenium-webdriver (4.38.0) + selenium-webdriver (4.35.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) - sentry-rails (6.2.0) - railties (>= 5.2.0) - sentry-ruby (~> 6.2.0) - sentry-ruby (6.2.0) + sentry-rails (5.26.0) + railties (>= 5.0) + sentry-ruby (~> 5.26.0) + sentry-ruby (5.26.0) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - sinatra (4.2.1) + sinatra (4.1.1) logger (>= 1.6.0) mustermann (~> 3.0) rack (>= 3.0.0, < 4) - rack-protection (= 4.2.1) + rack-protection (= 4.1.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - sqlite3 (2.8.1-aarch64-linux-gnu) - sqlite3 (2.8.1-arm64-darwin) - sqlite3 (2.8.1-x86_64-darwin) - sqlite3 (2.8.1-x86_64-linux-gnu) + sqlite3 (2.7.3-aarch64-linux-gnu) + sqlite3 (2.7.3-arm64-darwin) + sqlite3 (2.7.3-x86_64-darwin) + sqlite3 (2.7.3-x86_64-linux-gnu) stimulus-rails (1.3.4) railties (>= 6.0.0) stringio (3.1.8) thor (1.4.0) - thruster (0.1.16-aarch64-linux) - thruster (0.1.16-arm64-darwin) - thruster (0.1.16-x86_64-darwin) - thruster (0.1.16-x86_64-linux) + thruster (0.1.15-aarch64-linux) + thruster (0.1.15-arm64-darwin) + thruster (0.1.15-x86_64-darwin) + thruster (0.1.15-x86_64-linux) tilt (2.6.1) timeout (0.4.4) tsort (0.2.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (3.2.0) - unicode-emoji (~> 4.1) - unicode-emoji (4.1.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) uri (1.1.1) useragent (0.16.11) web-push (3.0.2) jwt (~> 3.0) openssl (~> 3.0) - webmock (3.26.1) + webmock (3.25.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -428,7 +428,7 @@ DEPENDENCIES puma (~> 6.6) rails! rails_autolink - redis (~> 5.0) + redis (~> 5.4) resque (~> 2.7.0) resque-pool (~> 0.7.1) rqrcode @@ -444,4 +444,4 @@ DEPENDENCIES webmock BUNDLED WITH - 2.7.2 + 2.5.9 From 7b7b3f8a67c152ce0373fe0a01c2a37b48bd3b14 Mon Sep 17 00:00:00 2001 From: Jason Zimdars Date: Mon, 1 Dec 2025 23:13:54 -0600 Subject: [PATCH 24/25] Rework toggle as switch --- app/helpers/accounts_helper.rb | 9 --------- app/views/accounts/edit.html.erb | 28 +++++++++++++++++++++++----- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index 22c6c4a..559e195 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -2,13 +2,4 @@ module AccountsHelper def account_logo_tag(style: nil) tag.figure image_tag(fresh_account_logo_path, alt: "Account logo", size: 300), class: "account-logo avatar #{style}" end - - def button_to_toggle_setting(label, setting) - button_to account_path(account: { settings: { setting => !Current.account.settings.send("#{setting}?") } }), - method: :put, - role: "checkbox", aria: { checked: true, labelledby: "#{setting}_account_setting" }, tabindex: 0, - class: "btn" do - tag.span(label, id: "#{setting}_account_setting") - end - end end diff --git a/app/views/accounts/edit.html.erb b/app/views/accounts/edit.html.erb index d4a4ab8..e0c2152 100644 --- a/app/views/accounts/edit.html.erb +++ b/app/views/accounts/edit.html.erb @@ -65,11 +65,29 @@ <% end %> - <% if Current.account.settings.restrict_room_creation_to_administrators? %> - <%= button_to_toggle_setting "Allow everyone to create new rooms", :restrict_room_creation_to_administrators %> - <% else %> - <%= button_to_toggle_setting "Only allow admins to create new rooms", :restrict_room_creation_to_administrators %> - <% end %> +
+ <%= form_with model: @account, method: :put, data: { controller: "form" }, class: "flex align-center gap center" do |form| %> +
+ <%= image_tag "crown.svg", aria: { hidden: "true" }, size: 18 %> Must be admin to create new rooms +
+ <%= form.fields_for :settings, @account.settings do |settings_form| %> + <%= settings_form.hidden_field :restrict_room_creation_to_administrators, + value: !Current.account.settings.restrict_room_creation_to_administrators? %> + <% end %> + + + + <% end %> +
<% else %> <%= account_logo_tag style: "txt-xx-large center" %>

<%= @account.name %>

From 550d4c75bd165a39e0961c2c0837f9d6ce97d0a9 Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Tue, 2 Dec 2025 08:22:32 +0100 Subject: [PATCH 25/25] Invert the icon color in dark mode --- app/views/accounts/edit.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/accounts/edit.html.erb b/app/views/accounts/edit.html.erb index e0c2152..1f9f868 100644 --- a/app/views/accounts/edit.html.erb +++ b/app/views/accounts/edit.html.erb @@ -68,7 +68,7 @@
<%= form_with model: @account, method: :put, data: { controller: "form" }, class: "flex align-center gap center" do |form| %>
- <%= image_tag "crown.svg", aria: { hidden: "true" }, size: 18 %> Must be admin to create new rooms + <%= image_tag "crown.svg", class: "colorize--black", aria: { hidden: "true" }, size: 18 %> Must be admin to create new rooms
<%= form.fields_for :settings, @account.settings do |settings_form| %> <%= settings_form.hidden_field :restrict_room_creation_to_administrators,