Compare commits

..

1 Commits

Author SHA1 Message Date
b31ddd39e4 Fix ArgumentError for text column length validations
`type_for_attribute(:description).limit` returns nil for text columns,
which newer Rails rejects with ArgumentError: :maximum must be a
non-negative Integer. Guard with `if type_for_attribute(...).limit`
to skip the validation when the column has no limit.

Affects Unit and Quantity models.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 12:28:54 +00:00
8 changed files with 9 additions and 48 deletions

View File

@@ -1,10 +1,6 @@
class User::ProfilesController < Devise::RegistrationsController class User::ProfilesController < Devise::RegistrationsController
def destroy def destroy
if current_user.sole_admin? # TODO: Disallow/disable deletion for last admin account; update :edit view
redirect_back fallback_location: edit_user_registration_path,
alert: t(".sole_admin")
return
end
super super
end end

View File

@@ -16,7 +16,7 @@ class Quantity < ApplicationRecord
end end
validates :name, presence: true, uniqueness: {scope: [:user_id, :parent_id]}, validates :name, presence: true, uniqueness: {scope: [:user_id, :parent_id]},
length: {maximum: type_for_attribute(:name).limit} length: {maximum: type_for_attribute(:name).limit}
validates :description, length: {maximum: type_for_attribute(:description).limit} validates :description, length: {maximum: type_for_attribute(:description).limit} if type_for_attribute(:description).limit
# Update :depths of progenies after parent change # Update :depths of progenies after parent change
before_save if: :parent_changed? do before_save if: :parent_changed? do

View File

@@ -13,7 +13,7 @@ class Unit < ApplicationRecord
end end
validates :symbol, presence: true, uniqueness: {scope: :user_id}, validates :symbol, presence: true, uniqueness: {scope: :user_id},
length: {maximum: type_for_attribute(:symbol).limit} length: {maximum: type_for_attribute(:symbol).limit}
validates :description, length: {maximum: type_for_attribute(:description).limit} validates :description, length: {maximum: type_for_attribute(:description).limit} if type_for_attribute(:description).limit
validates :multiplier, numericality: {equal_to: 1}, unless: :base validates :multiplier, numericality: {equal_to: 1}, unless: :base
validates :multiplier, numericality: {greater_than: 0, precision: true, scale: true}, if: :base validates :multiplier, numericality: {greater_than: 0, precision: true, scale: true}, if: :base

View File

@@ -29,11 +29,4 @@ class User < ApplicationRecord
def at_least(status) def at_least(status)
User.statuses[self.status] >= User.statuses[status] User.statuses[self.status] >= User.statuses[status]
end end
# Returns true when this user is the only admin account in the system.
# Used to block actions that would leave the application without an admin
# (account deletion, status demotion).
def sole_admin?
admin? && !User.admin.where.not(id: id).exists?
end
end end

View File

@@ -4,8 +4,9 @@
<% end %> <% end %>
<div class="rightside-area buttongrid"> <div class="rightside-area buttongrid">
<%= image_button_to_if !current_user.sole_admin?, t('.delete'), 'account-remove-outline', <%#= TODO: Disallow/disable deletion for last admin account, image_button_to_if %>
user_registration_path, form_class: 'tools-area', method: :delete, data: {turbo: false}, <%= image_button_to t('.delete'), 'account-remove-outline', user_registration_path,
form_class: 'tools-area', method: :delete, data: {turbo: false},
onclick: {confirm: t('.confirm_delete')} %> onclick: {confirm: t('.confirm_delete')} %>
</div> </div>

View File

@@ -162,9 +162,6 @@ en:
New password: New password:
<br><em>leave blank to keep unchanged</em> <br><em>leave blank to keep unchanged</em>
%{password_length_hint_html} %{password_length_hint_html}
registrations:
destroy:
sole_admin: You cannot delete the only admin account.
actions: Actions actions: Actions
add: Add add: Add
apply: Apply apply: Apply

View File

@@ -1,18 +0,0 @@
require "test_helper"
class RegistrationsControllerTest < ActionDispatch::IntegrationTest
test "sole admin cannot delete account" do
sign_in users(:admin)
delete user_registration_path
assert_redirected_to edit_user_registration_path
assert_equal t("registrations.destroy.sole_admin"), flash[:alert]
assert User.exists?(users(:admin).id)
end
test "non-admin can delete account" do
sign_in users(:alice)
assert_difference ->{ User.count }, -1 do
delete user_registration_path
end
end
end

View File

@@ -182,8 +182,8 @@ class UsersTest < ApplicationSystemTestCase
assert_title 'Access is forbidden to this page (403)' assert_title 'Access is forbidden to this page (403)'
end end
test "delete profile" do test 'delete profile' do
user = sign_in user: users.reject(&:admin?).select(&:confirmed?).sample user = sign_in
# TODO: remove condition after root_url changed to different path than # TODO: remove condition after root_url changed to different path than
# profile in routes.rb # profile in routes.rb
unless has_current_path?(edit_user_registration_path) unless has_current_path?(edit_user_registration_path)
@@ -196,15 +196,7 @@ class UsersTest < ApplicationSystemTestCase
assert_text t("devise.registrations.destroyed") assert_text t("devise.registrations.destroyed")
end end
test "sole admin cannot delete profile" do test 'index forbidden for non admin' do
sign_in user: users(:admin)
unless has_current_path?(edit_user_registration_path)
first(:link_or_button, users(:admin).email).click
end
assert find(:button, t("users.registrations.edit.delete"))[:disabled]
end
test "index forbidden for non admin" do
sign_in user: users.reject(&:admin?).select(&:confirmed?).sample sign_in user: users.reject(&:admin?).select(&:confirmed?).sample
visit users_path visit users_path
assert_title "Access is forbidden to this page (403)" assert_title "Access is forbidden to this page (403)"