Social media links:
diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb
index 849a515cc..ad69407cc 100644
--- a/app/views/users/_form.html.erb
+++ b/app/views/users/_form.html.erb
@@ -136,14 +136,30 @@
- <%= f.input :time_zone,
- as: :select,
- collection: us_time_zone_fundamentals,
- selected: f.object.time_zone.presence || "Pacific Time (US & Canada)",
- include_blank: false,
- hint: "Event times and other dates will be shown in this timezone.",
- input_html: { class: "w-full" },
- wrapper_html: { class: "w-full max-w-md" } %>
+ <% if @person %>
+
+
+
+
+ <%= @person.time_zone || "Pacific Time (US & Canada)" %>
+
+
+
+ Edit on <%= link_to "person profile", edit_person_path(@person), class: "underline" %>
+
+
+ <% else %>
+
+
+
+ Not available (no person associated with this user)
+
+
+ <% end %>
<% if f.object.persisted? %>
diff --git a/db/migrate/20260215070000_add_time_zone_to_people.rb b/db/migrate/20260215070000_add_time_zone_to_people.rb
new file mode 100644
index 000000000..be7f59216
--- /dev/null
+++ b/db/migrate/20260215070000_add_time_zone_to_people.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddTimeZoneToPeople < ActiveRecord::Migration[8.1]
+ def change
+ add_column :people, :time_zone, :string, default: "Pacific Time (US & Canada)"
+ end
+end
diff --git a/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb b/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
new file mode 100644
index 000000000..2bb3b39e7
--- /dev/null
+++ b/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CopyTimeZoneFromUsersToPeople < ActiveRecord::Migration[8.1]
+ def up
+ # Copy time_zone from users to people where user has a person
+ execute <<-SQL
+ UPDATE people
+ INNER JOIN users ON users.person_id = people.id
+ SET people.time_zone = users.time_zone
+ WHERE users.time_zone IS NOT NULL
+ SQL
+ end
+
+ def down
+ # No need to reverse this data migration
+ end
+end
diff --git a/db/migrate/20260215070002_remove_time_zone_from_users.rb b/db/migrate/20260215070002_remove_time_zone_from_users.rb
new file mode 100644
index 000000000..91b2e5392
--- /dev/null
+++ b/db/migrate/20260215070002_remove_time_zone_from_users.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class RemoveTimeZoneFromUsers < ActiveRecord::Migration[8.1]
+ def change
+ remove_column :users, :time_zone, :string
+ end
+end
From c1dae8d7bda140b923002e5ae9bdf0f42221d05e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 06:58:24 +0000
Subject: [PATCH 2/8] Update tests for time_zone migration to Person
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
spec/models/person_spec.rb | 20 ++++++++++++++++++++
spec/requests/users_spec.rb | 7 -------
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/spec/models/person_spec.rb b/spec/models/person_spec.rb
index 6a9a34fc8..dc9b9d805 100644
--- a/spec/models/person_spec.rb
+++ b/spec/models/person_spec.rb
@@ -47,6 +47,26 @@
it { should allow_value("").for(:email_2) }
it { should_not allow_value("not-an-email").for(:email_2).with_message("must be a valid email address") }
+ describe "time_zone validation" do
+ let(:admin) { create(:user, :admin) }
+
+ it "accepts valid time zones" do
+ person = build(:person, time_zone: "Pacific Time (US & Canada)", created_by: admin, updated_by: admin)
+ expect(person).to be_valid
+ end
+
+ it "rejects invalid time zones" do
+ person = build(:person, time_zone: "Invalid/Zone", created_by: admin, updated_by: admin)
+ expect(person).not_to be_valid
+ expect(person.errors[:time_zone]).to include("is not a valid time zone")
+ end
+
+ it "allows nil time zone" do
+ person = build(:person, time_zone: nil, created_by: admin, updated_by: admin)
+ expect(person).to be_valid
+ end
+ end
+
describe "unique_name_and_email_combination" do
let(:admin) { create(:user, :admin) }
diff --git a/spec/requests/users_spec.rb b/spec/requests/users_spec.rb
index 40a5dedc1..1a269804b 100644
--- a/spec/requests/users_spec.rb
+++ b/spec/requests/users_spec.rb
@@ -228,13 +228,6 @@
patch user_url(user), params: { user: new_attributes }
expect(response).to redirect_to(users_url)
end
-
- it "permits and updates time_zone" do
- user = User.create! valid_attributes
- patch user_url(user), params: { user: { time_zone: "Eastern Time (US & Canada)" } }
- user.reload
- expect(user.time_zone).to eq("Eastern Time (US & Canada)")
- end
end
context "with invalid parameters" do
From 35e3e0600e5e2a6eadde388e267a5dcfc6d43926 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 06:59:48 +0000
Subject: [PATCH 3/8] Fix time_zone validation and make migration
database-agnostic
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
app/models/person.rb | 2 +-
...260215070001_copy_time_zone_from_users_to_people.rb | 10 ++++------
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/app/models/person.rb b/app/models/person.rb
index 10b65fe6f..a65605997 100644
--- a/app/models/person.rb
+++ b/app/models/person.rb
@@ -160,7 +160,7 @@ def strip_whitespace
end
def time_zone_must_be_valid
- return if ActiveSupport::TimeZone[time_zone]
+ return if time_zone.blank? || ActiveSupport::TimeZone[time_zone]
errors.add(:time_zone, "is not a valid time zone")
end
diff --git a/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb b/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
index 2bb3b39e7..84ca5e4ad 100644
--- a/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
+++ b/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
@@ -3,12 +3,10 @@
class CopyTimeZoneFromUsersToPeople < ActiveRecord::Migration[8.1]
def up
# Copy time_zone from users to people where user has a person
- execute <<-SQL
- UPDATE people
- INNER JOIN users ON users.person_id = people.id
- SET people.time_zone = users.time_zone
- WHERE users.time_zone IS NOT NULL
- SQL
+ # Using ActiveRecord for database compatibility
+ User.where.not(person_id: nil).where.not(time_zone: nil).find_each do |user|
+ Person.where(id: user.person_id).update_all(time_zone: user.time_zone)
+ end
end
def down
From 5880a17ed4a0d2453bf784bc35abeadc0f7a70d8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 07:00:28 +0000
Subject: [PATCH 4/8] Optimize data migration for better performance
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
...001_copy_time_zone_from_users_to_people.rb | 20 ++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb b/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
index 84ca5e4ad..8a386b879 100644
--- a/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
+++ b/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
@@ -3,9 +3,23 @@
class CopyTimeZoneFromUsersToPeople < ActiveRecord::Migration[8.1]
def up
# Copy time_zone from users to people where user has a person
- # Using ActiveRecord for database compatibility
- User.where.not(person_id: nil).where.not(time_zone: nil).find_each do |user|
- Person.where(id: user.person_id).update_all(time_zone: user.time_zone)
+ # Using raw SQL for efficiency while being database-agnostic
+ if ActiveRecord::Base.connection.adapter_name.downcase.include?("mysql")
+ execute <<-SQL.squish
+ UPDATE people
+ INNER JOIN users ON users.person_id = people.id
+ SET people.time_zone = users.time_zone
+ WHERE users.time_zone IS NOT NULL
+ SQL
+ else
+ # PostgreSQL syntax
+ execute <<-SQL.squish
+ UPDATE people
+ SET time_zone = users.time_zone
+ FROM users
+ WHERE users.person_id = people.id
+ AND users.time_zone IS NOT NULL
+ SQL
end
end
From e88cc632e17532332735107113299018ae168b62 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 15 Feb 2026 21:17:23 +0000
Subject: [PATCH 5/8] Use nested attributes for time_zone on person form
instead of moving field
Co-authored-by: maebeale <7607813+maebeale@users.noreply.github.com>
---
app/controllers/application_controller.rb | 2 +-
app/controllers/people_controller.rb | 4 +--
app/controllers/users_controller.rb | 2 +-
app/models/person.rb | 7 -----
app/models/user.rb | 7 +++++
app/views/people/_form.html.erb | 24 ++++++++-------
app/views/users/_form.html.erb | 30 ++++++++++---------
.../20260215070000_add_time_zone_to_people.rb | 7 -----
...001_copy_time_zone_from_users_to_people.rb | 29 ------------------
...60215070002_remove_time_zone_from_users.rb | 7 -----
spec/models/person_spec.rb | 20 -------------
spec/requests/users_spec.rb | 7 +++++
12 files changed, 48 insertions(+), 98 deletions(-)
delete mode 100644 db/migrate/20260215070000_add_time_zone_to_people.rb
delete mode 100644 db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
delete mode 100644 db/migrate/20260215070002_remove_time_zone_from_users.rb
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e98921ea0..878e793ad 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -32,7 +32,7 @@ def after_sign_out_path_for(resource_or_scope)
around_action :set_time_zone_from_user
def set_time_zone_from_user
- zone = ActiveSupport::TimeZone[current_user&.person&.time_zone || "UTC"]
+ zone = ActiveSupport::TimeZone[current_user&.time_zone || "UTC"]
if zone
Time.use_zone(zone) { yield }
else
diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
index 1625b0e69..93dcf921a 100644
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -295,7 +295,6 @@ def person_params
:bio, :notes,
:display_name_preference,
:pronouns,
- :time_zone,
:profile_show_name_preference,
:profile_is_searchable,
:profile_show_pronouns,
@@ -370,7 +369,8 @@ def person_params
:city2,
:state2,
:zip2,
- :notes
+ :notes,
+ :time_zone
],
affiliations_attributes: [
:id,
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 14e34f0c8..6d47ddc22 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -325,7 +325,7 @@ def find_duplicate_users(email, exclude_person_id: nil)
def user_params
params.require(:user).permit(
- :email, :comment, :person_id, :inactive, :locked, :primary_address, :super_user,
+ :email, :comment, :person_id, :inactive, :locked, :primary_address, :time_zone, :super_user,
##### legacy to remove later
:agency_id, :legacy, :legacy_id, :subscribecode, :avatar, :first_name, :last_name, # legacy to remove later
diff --git a/app/models/person.rb b/app/models/person.rb
index a65605997..90e43da50 100644
--- a/app/models/person.rb
+++ b/app/models/person.rb
@@ -42,7 +42,6 @@ class Person < ApplicationRecord
validates :last_name, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP, message: "must be a valid email address" }, allow_blank: true
validates :email_2, format: { with: URI::MailTo::EMAIL_REGEXP, message: "must be a valid email address" }, allow_blank: true
- validate :time_zone_must_be_valid, if: :time_zone_changed?
validate :unique_name_and_email_combination
CONTACT_TYPES = [ "work", "personal" ].freeze
@@ -159,12 +158,6 @@ def strip_whitespace
self.email_2 = email_2&.strip
end
- def time_zone_must_be_valid
- return if time_zone.blank? || ActiveSupport::TimeZone[time_zone]
-
- errors.add(:time_zone, "is not a valid time zone")
- end
-
def unique_name_and_email_combination
return unless first_name.present? && last_name.present?
diff --git a/app/models/user.rb b/app/models/user.rb
index 017868016..adc9f1f6e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -67,6 +67,7 @@ class User < ApplicationRecord
# Validations
validates :email, presence: true, uniqueness: { case_sensitive: false }
+ validate :time_zone_must_be_valid, if: :time_zone_changed?
validate :person_id_must_be_present_if_previously_set, on: :update
validates_associated :person, if: -> { person.present? }
@@ -206,6 +207,12 @@ def strip_whitespace
self.email = email&.strip
end
+ def time_zone_must_be_valid
+ return if ActiveSupport::TimeZone[time_zone]
+
+ errors.add(:time_zone, "is not a valid time zone")
+ end
+
def person_id_must_be_present_if_previously_set
return unless person_id_was.present?
return if person_id.present?
diff --git a/app/views/people/_form.html.erb b/app/views/people/_form.html.erb
index 211a496ed..a48ea0c20 100644
--- a/app/views/people/_form.html.erb
+++ b/app/views/people/_form.html.erb
@@ -210,16 +210,20 @@
hint: "Maximum 250 characters" %>
- <%= f.input :time_zone,
- as: :select,
- collection: us_time_zone_fundamentals,
- selected: f.object.time_zone.presence || "Pacific Time (US & Canada)",
- include_blank: false,
- hint: "Event times and other dates will be shown in this timezone.",
- input_html: { class: "w-full" },
- wrapper_html: { class: "w-full max-w-md" } %>
-
+ <%= user_form.input :time_zone,
+ as: :select,
+ collection: us_time_zone_fundamentals,
+ selected: user_form.object.time_zone.presence || "Pacific Time (US & Canada)",
+ include_blank: false,
+ hint: "Event times and other dates will be shown in this timezone.",
+ input_html: { class: "w-full" },
+ wrapper_html: { class: "w-full max-w-md" } %>
+
diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb
index ad69407cc..385fbefc6 100644
--- a/app/views/users/_form.html.erb
+++ b/app/views/users/_form.html.erb
@@ -135,32 +135,34 @@
-
- <% if @person %>
+ <% if @person %>
+
- <%= @person.time_zone || "Pacific Time (US & Canada)" %>
+ <%= f.object.time_zone || "Pacific Time (US & Canada)" %>
Edit on <%= link_to "person profile", edit_person_path(@person), class: "underline" %>
- <% else %>
-
-
-
- Not available (no person associated with this user)
-
-
- <% end %>
-
+
+ <% else %>
+
+ <%= f.input :time_zone,
+ as: :select,
+ collection: us_time_zone_fundamentals,
+ selected: f.object.time_zone.presence || "Pacific Time (US & Canada)",
+ include_blank: false,
+ hint: "Event times and other dates will be shown in this timezone.",
+ input_html: { class: "w-full" },
+ wrapper_html: { class: "w-full max-w-md" } %>
+
+ <% end %>
<% if f.object.persisted? %>
diff --git a/db/migrate/20260215070000_add_time_zone_to_people.rb b/db/migrate/20260215070000_add_time_zone_to_people.rb
deleted file mode 100644
index be7f59216..000000000
--- a/db/migrate/20260215070000_add_time_zone_to_people.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class AddTimeZoneToPeople < ActiveRecord::Migration[8.1]
- def change
- add_column :people, :time_zone, :string, default: "Pacific Time (US & Canada)"
- end
-end
diff --git a/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb b/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
deleted file mode 100644
index 8a386b879..000000000
--- a/db/migrate/20260215070001_copy_time_zone_from_users_to_people.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# frozen_string_literal: true
-
-class CopyTimeZoneFromUsersToPeople < ActiveRecord::Migration[8.1]
- def up
- # Copy time_zone from users to people where user has a person
- # Using raw SQL for efficiency while being database-agnostic
- if ActiveRecord::Base.connection.adapter_name.downcase.include?("mysql")
- execute <<-SQL.squish
- UPDATE people
- INNER JOIN users ON users.person_id = people.id
- SET people.time_zone = users.time_zone
- WHERE users.time_zone IS NOT NULL
- SQL
- else
- # PostgreSQL syntax
- execute <<-SQL.squish
- UPDATE people
- SET time_zone = users.time_zone
- FROM users
- WHERE users.person_id = people.id
- AND users.time_zone IS NOT NULL
- SQL
- end
- end
-
- def down
- # No need to reverse this data migration
- end
-end
diff --git a/db/migrate/20260215070002_remove_time_zone_from_users.rb b/db/migrate/20260215070002_remove_time_zone_from_users.rb
deleted file mode 100644
index 91b2e5392..000000000
--- a/db/migrate/20260215070002_remove_time_zone_from_users.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class RemoveTimeZoneFromUsers < ActiveRecord::Migration[8.1]
- def change
- remove_column :users, :time_zone, :string
- end
-end
diff --git a/spec/models/person_spec.rb b/spec/models/person_spec.rb
index dc9b9d805..6a9a34fc8 100644
--- a/spec/models/person_spec.rb
+++ b/spec/models/person_spec.rb
@@ -47,26 +47,6 @@
it { should allow_value("").for(:email_2) }
it { should_not allow_value("not-an-email").for(:email_2).with_message("must be a valid email address") }
- describe "time_zone validation" do
- let(:admin) { create(:user, :admin) }
-
- it "accepts valid time zones" do
- person = build(:person, time_zone: "Pacific Time (US & Canada)", created_by: admin, updated_by: admin)
- expect(person).to be_valid
- end
-
- it "rejects invalid time zones" do
- person = build(:person, time_zone: "Invalid/Zone", created_by: admin, updated_by: admin)
- expect(person).not_to be_valid
- expect(person.errors[:time_zone]).to include("is not a valid time zone")
- end
-
- it "allows nil time zone" do
- person = build(:person, time_zone: nil, created_by: admin, updated_by: admin)
- expect(person).to be_valid
- end
- end
-
describe "unique_name_and_email_combination" do
let(:admin) { create(:user, :admin) }
diff --git a/spec/requests/users_spec.rb b/spec/requests/users_spec.rb
index 1a269804b..40a5dedc1 100644
--- a/spec/requests/users_spec.rb
+++ b/spec/requests/users_spec.rb
@@ -228,6 +228,13 @@
patch user_url(user), params: { user: new_attributes }
expect(response).to redirect_to(users_url)
end
+
+ it "permits and updates time_zone" do
+ user = User.create! valid_attributes
+ patch user_url(user), params: { user: { time_zone: "Eastern Time (US & Canada)" } }
+ user.reload
+ expect(user.time_zone).to eq("Eastern Time (US & Canada)")
+ end
end
context "with invalid parameters" do
From d77eec06b78f07dd7024d7e9e13b0a4b7970564a Mon Sep 17 00:00:00 2001
From: maebeale
Date: Mon, 16 Feb 2026 17:54:50 -0500
Subject: [PATCH 6/8] More explicitly handle user params sent over (e.g.
timezone) when going from user to person create
---
app/controllers/people_controller.rb | 3 +++
1 file changed, 3 insertions(+)
diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
index 93dcf921a..997a3a457 100644
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -90,6 +90,9 @@ def create
authorize! @person
@person.user ||= User.find_by(id: params[:user_id]) if params[:user_id].present?
@person.user ||= User.find_by(id: params.dig(:person, :user_attributes, :id)) if params.dig(:person, :user_attributes, :id).present?
+ if @person.user && person_params[:user_attributes].present?
+ @person.user.assign_attributes(person_params[:user_attributes].except(:id))
+ end
unless params[:skip_duplicate_check].present?
duplicates = find_duplicate_people(
From be870bc7ae7ebb4d81a76bf6973caa1e5c87066d Mon Sep 17 00:00:00 2001
From: maebeale
Date: Mon, 16 Feb 2026 17:55:34 -0500
Subject: [PATCH 7/8] Add spec for this flow
---
spec/requests/people_create_spec.rb | 31 +++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 spec/requests/people_create_spec.rb
diff --git a/spec/requests/people_create_spec.rb b/spec/requests/people_create_spec.rb
new file mode 100644
index 000000000..6ac5b5b41
--- /dev/null
+++ b/spec/requests/people_create_spec.rb
@@ -0,0 +1,31 @@
+require "rails_helper"
+
+RSpec.describe "POST /people", type: :request do
+ let(:admin) { create(:user, :admin) }
+
+ before { sign_in admin }
+
+ describe "user_attributes are applied on create" do
+ it "updates the user's time_zone from the person form" do
+ user = create(:user, time_zone: "Hawaii")
+
+ post people_path, params: {
+ skip_duplicate_check: "1",
+ person: {
+ first_name: user.first_name,
+ last_name: user.last_name,
+ email: user.email,
+ created_by_id: admin.id,
+ updated_by_id: admin.id,
+ user_attributes: {
+ id: user.id,
+ time_zone: "Eastern Time (US & Canada)"
+ }
+ }
+ }
+
+ expect(response).to redirect_to(Person.last)
+ expect(user.reload.time_zone).to eq("Eastern Time (US & Canada)")
+ end
+ end
+end
From ef5d001d9430b1d49507bd52b4e71cfe15425913 Mon Sep 17 00:00:00 2001
From: maebeale
Date: Mon, 16 Feb 2026 18:05:24 -0500
Subject: [PATCH 8/8] Assign user if not already on new
---
app/controllers/people_controller.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
index 997a3a457..2dcc7da3d 100644
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -67,6 +67,7 @@ def show
def new
set_user
@person = @user ? PersonFromUserService.new(user: @user).call : Person.new
+ @person.user = @user if @user
authorize! @person
set_form_variables
end