[deploy] Add optional fields to user (#7246)

* add optional fields to user

- this provides the back-end implementation for #2365

* fix mistakes in schema.rb

* renamed OptionalField to userOptionalField

* rename field column in user_optional_fields to label

* add uniqueness validation on user_optional_field labels

* scope flag uniqueness to user_id

* add indexing on label in user_optional_fields

* add optional fields to user

- this provides the back-end implementation for #2365

* fix mistakes in schema.rb

* add index on user_id for user_optional_fields

* Remove duplicate model

* Fix schema

* Re-add past indexes

* Remove old index

* Remove another old index

* add `null:false` to columns in optional fields

* add `null: false` to the migration

- Updated test to account for the null constraint

* add null:false to user_id on user_optional_tables

Co-authored-by: rhymes <rhymesete@gmail.com>
Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
This commit is contained in:
Yechiel Kalmenson 2020-04-28 15:42:41 -04:00 committed by GitHub
parent 3311023163
commit 131d11aa73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 76 additions and 1 deletions

View file

@ -73,6 +73,7 @@ class User < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :notes, as: :noteable, inverse_of: :noteable
has_many :notification_subscriptions, dependent: :destroy
has_many :user_optional_fields, dependent: :destroy
has_many :notifications, dependent: :destroy
has_many :offender_feedback_messages, class_name: "FeedbackMessage", inverse_of: :offender, foreign_key: :offender_id, dependent: :nullify
has_many :organization_memberships, dependent: :destroy

View file

@ -0,0 +1,13 @@
class UserOptionalField < ApplicationRecord
belongs_to :user
validates :label, presence: true, length: { maximum: 30 }, uniqueness: { scope: :user_id }
validates :value, presence: true, length: { maximum: 128 }
validate :validate_quota
def validate_quota
return unless user
errors.add(:user_id, "already has the maximum allowable optional fields") unless user.user_optional_fields.count < 3
end
end

View file

@ -0,0 +1,13 @@
class CreateUserOptionalFields < ActiveRecord::Migration[5.2]
def change
create_table :user_optional_fields do |t|
t.string :label, null: false
t.string :value, null: false
t.references :user, foreign_key: true, null: false
t.timestamps
end
add_index :user_optional_fields, [:label, :user_id], unique: true
end
end

View file

@ -1086,6 +1086,16 @@ ActiveRecord::Schema.define(version: 2020_04_26_124118) do
t.index ["user_id"], name: "index_user_counters_on_user_id", unique: true
end
create_table "user_optional_fields", force: :cascade do |t|
t.datetime "created_at", null: false
t.string "label", null: false
t.datetime "updated_at", null: false
t.bigint "user_id", null: false
t.string "value", null: false
t.index ["label", "user_id"], name: "index_user_optional_fields_on_label_and_user_id", unique: true
t.index ["user_id"], name: "index_user_optional_fields_on_user_id"
end
create_table "users", id: :serial, force: :cascade do |t|
t.integer "articles_count", default: 0, null: false
t.string "available_for"
@ -1268,6 +1278,7 @@ ActiveRecord::Schema.define(version: 2020_04_26_124118) do
add_foreign_key "user_blocks", "users", column: "blocked_id"
add_foreign_key "user_blocks", "users", column: "blocker_id"
add_foreign_key "user_counters", "users", on_delete: :cascade
add_foreign_key "user_optional_fields", "users"
add_foreign_key "users_roles", "users", on_delete: :cascade
add_foreign_key "webhook_endpoints", "oauth_applications"
add_foreign_key "webhook_endpoints", "users"

View file

@ -0,0 +1,7 @@
FactoryBot.define do
factory :user_optional_field do
user
label { "Pronoun" }
value { "They/them" }
end
end

View file

@ -136,5 +136,13 @@ FactoryBot.define do
user.add_role :tag_moderator, tag
end
end
trait :with_user_optional_fields do
after(:create) do |user|
create(:user_optional_field, user: user)
create(:user_optional_field, user: user, label: "another field1", value: "another value1")
create(:user_optional_field, user: user, label: "another field2", value: "another value2")
end
end
end
end

View file

@ -0,0 +1,14 @@
require "rails_helper"
RSpec.describe UserOptionalField, type: :model do
subject { user_optional_field }
let(:user_optional_field) { create(:user_optional_field) }
it { is_expected.to belong_to(:user) }
it { is_expected.to validate_presence_of(:label) }
it { is_expected.to validate_uniqueness_of(:label).scoped_to(:user_id) }
it { is_expected.to validate_length_of(:label).is_at_most(30) }
it { is_expected.to validate_presence_of(:value) }
it { is_expected.to validate_length_of(:value).is_at_most(128) }
end

View file

@ -12,6 +12,7 @@ end
RSpec.describe User, type: :model do
let(:user) { create(:user) }
let(:other_user) { create(:user) }
let(:user_with_user_optional_fields) { create(:user, :with_user_optional_fields) }
let(:org) { create(:organization) }
before { mock_auth_hash }
@ -132,9 +133,16 @@ RSpec.describe User, type: :model do
end
# rubocop:enable RSpec/NamedSubject
it "has at most three optional fields" do
expect(user_with_user_optional_fields).to have_many(:user_optional_fields).dependent(:destroy)
fourth_field = user_with_user_optional_fields.user_optional_fields.create(label: "some field", value: "some value")
expect(fourth_field).not_to be_valid
end
it { is_expected.to have_many(:organization_memberships).dependent(:destroy) }
it { is_expected.to have_one(:counters).class_name("UserCounter").dependent(:destroy) }
it { is_expected.to have_one(:pro_membership).dependent(:destroy) }
it { is_expected.to validate_uniqueness_of(:username).case_insensitive }
it { is_expected.not_to allow_value("#xyz").for(:bg_color_hex) }
it { is_expected.not_to allow_value("#xyz").for(:text_color_hex) }
it { is_expected.not_to allow_value("AcMe_1%").for(:username) }