User Settings Step 1: Add user_settings and user_notification_settings tables (rfc#32) (#12768)

* feat: add the two new models users_setting and users_notification_setting

* feat: add the settings and notification_settings table to the schema

* feat: add the user and notification models

* feat: add the user_id foreign key to the model

* chore: sneaky indent

* feat: add some fields from the profile attributes

* Revert "feat: add some fields from the profile attributes"

This reverts commit 376828746ded063a243505d317140fa5339227cf.

* chore: add some profile field attributes

* chore: remove language_settings

* chore: update indent

* chore: remove language_settings

* feat: changes to the tables

* chore:  remove validation in favor of the foreign keys

* chore: add default for editor version

* Address PR review suggestions

* setting_spec.rb needs to be fixed; need help

* Working on PR review comments

* Continue with addressing PR review comments

* Remove normalize_config_values method; pass correct values from forms

* Address Travis failures

* revert some unnecessary changes in spec file

Co-authored-by: Arit Amana <msarit@gmail.com>
This commit is contained in:
Ridhwana 2021-03-31 14:37:46 +02:00 committed by GitHub
parent d77b21b9b5
commit 4848a8b2e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 247 additions and 8 deletions

View file

@ -108,6 +108,8 @@ class User < ApplicationRecord
acts_as_follower
has_one :profile, dependent: :destroy
has_one :notification_setting, class_name: "Users::NotificationSetting", dependent: :destroy
has_one :setting, class_name: "Users::Setting", dependent: :destroy
has_many :access_grants, class_name: "Doorkeeper::AccessGrant", foreign_key: :resource_owner_id,
inverse_of: :resource_owner, dependent: :delete_all

View file

@ -0,0 +1,14 @@
module Users
class NotificationSetting < ApplicationRecord
self.table_name_prefix = "users_"
validates :email_digest_periodic, inclusion: { in: [true, false] }
alias_attribute :subscribed_to_welcome_notifications?, :welcome_notifications
alias_attribute :subscribed_to_mod_roundrobin_notifications?, :mod_roundrobin_notifications
alias_attribute :subscribed_to_email_follower_notifications?, :email_follower_notifications
# NOTE: @ridhwana Need to account for
# subscribe_to_mailchimp_newsletter in app/models/user.rb
# code: return unless saved_changes.key?(:email) || saved_changes.key?(:email_newsletter)
end
end

View file

@ -0,0 +1,33 @@
module Users
class Setting < ApplicationRecord
self.table_name_prefix = "users_"
belongs_to :user
enum editor_version: { v2: 0, v1: 1 }, _suffix: :editor
enum config_font: { default: 0, comic_sans: 1, monospace: 2, open_dyslexic: 3, sans_serif: 4, serif: 5 }
enum inbox_type: { private: 0, open: 1 }, _suffix: :inbox
enum config_navbar: { default_navbar: 0, static_navbar: 1 }
enum config_theme: { default_theme: 0, minimal_light_theme: 1, night_theme: 2, pink_theme: 3,
ten_x_hacker_theme: 4 }
validates :user_id, presence: true
validates :experience_level, numericality: { less_than_or_equal_to: 10 }, allow_blank: true
validates :feed_referential_link, inclusion: { in: [true, false] }
validates :feed_url, length: { maximum: 500 }, allow_nil: true
validates :inbox_guidelines, length: { maximum: 250 }, allow_nil: true
validate :validate_feed_url, if: :feed_url_changed?
private
def validate_feed_url
return if feed_url.blank?
valid = Feeds::ValidateUrl.call(feed_url)
errors.add(:feed_url, "is not a valid RSS/Atom feed") unless valid
rescue StandardError => e
errors.add(:feed_url, e.message)
end
end
end

View file

@ -11,7 +11,7 @@
<fieldset class="crayons-field">
<legend class="crayons-field__label">Site Theme</legend>
<div class="theme-selector-field grid gap-4 grid-cols-2 l:grid-cols-3">
<% ["default", "night theme", "minimal light theme", "pink theme", "ten x hacker theme"].each do |theme| %>
<% %w[default night_theme minimal_light_theme pink_theme ten_x_hacker_theme].each do |theme| %>
<%= render partial: "theme_selector", locals: { f: f, theme: theme } %>
<% end %>
</div>

View file

@ -1,5 +1,5 @@
<label class="rich-selector crayons-field crayons-field--radio navbar-selector--<%= navbar.tr(" ", "-") %>">
<%= f.radio_button :config_navbar, navbar, checked: @user.config_navbar.tr("_", " ") == navbar, class: "crayons-radio" %>
<label class="rich-selector crayons-field crayons-field--radio navbar-selector--<%= navbar %>">
<%= f.radio_button :config_navbar, navbar, checked: @user.config_navbar == navbar, class: "crayons-radio" %>
<div class="crayons-field__label">
<% if navbar == "default" %>
Fixed to window

View file

@ -1,5 +1,5 @@
<label class="rich-selector crayons-field crayons-field--radio p-4 theme-selector theme-selector--<%= theme.tr(" ", "-") %>" title="<%= theme.titlecase %>">
<%= f.radio_button :config_theme, theme, checked: @user.config_theme.tr("_", " ") == theme, class: "crayons-radio" %>
<label class="rich-selector crayons-field crayons-field--radio p-4 theme-selector theme-selector--<%= theme.tr("_", "-") %>" title="<%= theme.titlecase %>">
<%= f.radio_button :config_theme, theme, checked: @user.config_theme == theme, class: "crayons-radio" %>
<div class="crayons-field__label">
<%= theme.titlecase %>

View file

@ -0,0 +1,29 @@
class CreateUsersSettings < ActiveRecord::Migration[6.0]
def change
create_table :users_settings do |t|
t.references :user, foreign_key: true, null: false
t.boolean "display_announcements", default: true, null: false
t.boolean "display_sponsors", default: true, null: false
t.integer "editor_version", default: 0, null: false
t.integer "config_font", default: 0, null: false
t.integer "inbox_type", default: 0, null: false
t.integer "config_navbar", default: 0, null: false
t.integer "config_theme", default: 0, null: false
t.integer "experience_level"
t.boolean "feed_mark_canonical", default: false, null: false
t.boolean "feed_referential_link", default: true, null: false
t.string "feed_url"
t.string "inbox_guidelines"
t.boolean "permit_adjacent_sponsors", default: true
t.string "brand_color1", default: "#000000"
t.string "brand_color2", default: "#ffffff"
t.boolean "display_email_on_profile", default: false, null: false
t.timestamps
end
end
end

View file

@ -0,0 +1,24 @@
class CreateUsersNotificationSettings < ActiveRecord::Migration[6.0]
def change
create_table :users_notification_settings do |t|
t.references :user, foreign_key: true, null: false
t.boolean "email_badge_notifications", default: true, null: false
t.boolean "email_comment_notifications", default: true, null: false
t.boolean "email_community_mod_newsletter", default: false, null: false
t.boolean "email_connect_messages", default: true, null: false
t.boolean "email_digest_periodic", default: false, null: false
t.boolean "email_follower_notifications", default: true, null: false
t.boolean "email_membership_newsletter", default: false, null: false
t.boolean "email_mention_notifications", default: true, null: false
t.boolean "email_newsletter", default: false, null: false
t.boolean "email_tag_mod_newsletter", default: false, null: false
t.boolean "email_unread_notifications", default: true, null: false
t.boolean "mobile_comment_notifications", default: true, null: false
t.boolean "mod_roundrobin_notifications", default: true, null: false
t.boolean "reaction_notifications", default: true, null: false
t.boolean "welcome_notifications", default: true, null: false
t.timestamps
end
end
end

View file

@ -432,12 +432,12 @@ ActiveRecord::Schema.define(version: 2021_03_26_172406) do
end
create_table "devices", force: :cascade do |t|
t.bigint "user_id", null: false
t.string "token", null: false
t.string "platform", null: false
t.string "app_bundle", null: false
t.datetime "created_at", precision: 6, null: false
t.string "platform", null: false
t.string "token", null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "user_id", null: false
t.index ["user_id", "token", "platform", "app_bundle"], name: "index_devices_on_user_id_and_token_and_platform_and_app_bundle", unique: true
end
@ -1363,12 +1363,57 @@ ActiveRecord::Schema.define(version: 2021_03_26_172406) do
t.string "username"
end
create_table "users_notification_settings", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.boolean "email_badge_notifications", default: true, null: false
t.boolean "email_comment_notifications", default: true, null: false
t.boolean "email_community_mod_newsletter", default: false, null: false
t.boolean "email_connect_messages", default: true, null: false
t.boolean "email_digest_periodic", default: false, null: false
t.boolean "email_follower_notifications", default: true, null: false
t.boolean "email_membership_newsletter", default: false, null: false
t.boolean "email_mention_notifications", default: true, null: false
t.boolean "email_newsletter", default: false, null: false
t.boolean "email_tag_mod_newsletter", default: false, null: false
t.boolean "email_unread_notifications", default: true, null: false
t.boolean "mobile_comment_notifications", default: true, null: false
t.boolean "mod_roundrobin_notifications", default: true, null: false
t.boolean "reaction_notifications", default: true, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "user_id", null: false
t.boolean "welcome_notifications", default: true, null: false
t.index ["user_id"], name: "index_users_notification_settings_on_user_id"
end
create_table "users_roles", id: false, force: :cascade do |t|
t.bigint "role_id"
t.bigint "user_id"
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
end
create_table "users_settings", force: :cascade do |t|
t.string "brand_color1", default: "#000000"
t.string "brand_color2", default: "#ffffff"
t.integer "config_font", default: 0, null: false
t.integer "config_navbar", default: 0, null: false
t.integer "config_theme", default: 0, null: false
t.datetime "created_at", precision: 6, null: false
t.boolean "display_announcements", default: true, null: false
t.boolean "display_email_on_profile", default: false, null: false
t.boolean "display_sponsors", default: true, null: false
t.integer "editor_version", default: 0, null: false
t.integer "experience_level"
t.boolean "feed_mark_canonical", default: false, null: false
t.boolean "feed_referential_link", default: true, null: false
t.string "feed_url"
t.string "inbox_guidelines"
t.integer "inbox_type", default: 0, null: false
t.boolean "permit_adjacent_sponsors", default: true
t.datetime "updated_at", precision: 6, null: false
t.bigint "user_id", null: false
t.index ["user_id"], name: "index_users_settings_on_user_id"
end
create_table "users_suspended_usernames", primary_key: "username_hash", id: :string, force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
@ -1480,8 +1525,10 @@ ActiveRecord::Schema.define(version: 2021_03_26_172406) do
add_foreign_key "user_blocks", "users", column: "blocker_id"
add_foreign_key "user_subscriptions", "users", column: "author_id"
add_foreign_key "user_subscriptions", "users", column: "subscriber_id"
add_foreign_key "users_notification_settings", "users"
add_foreign_key "users_roles", "roles", on_delete: :cascade
add_foreign_key "users_roles", "users", on_delete: :cascade
add_foreign_key "users_settings", "users"
add_foreign_key "webhook_endpoints", "oauth_applications"
add_foreign_key "webhook_endpoints", "users"
end

View file

@ -0,0 +1,15 @@
FactoryBot.define do
factory :users_setting, class: "Users::Setting" do
config_font { "sans_serif" }
config_navbar { "default_navbar" }
config_theme { "night_theme" }
display_announcements { true }
display_sponsors { true }
editor_version { "v1" }
experience_level { 1 }
feed_mark_canonical { false }
feed_referential_link { true }
inbox_type { "private" }
permit_adjacent_sponsors { true }
end
end

View file

@ -38,6 +38,8 @@ RSpec.describe User, type: :model do
subject { user }
it { is_expected.to have_one(:profile).dependent(:destroy) }
it { is_expected.to have_one(:notification_setting).dependent(:destroy) }
it { is_expected.to have_one(:setting).dependent(:destroy) }
it { is_expected.to have_many(:access_grants).class_name("Doorkeeper::AccessGrant").dependent(:delete_all) }
it { is_expected.to have_many(:access_tokens).class_name("Doorkeeper::AccessToken").dependent(:delete_all) }

View file

@ -0,0 +1,73 @@
require "rails_helper"
# rubocop:disable Layout/LineLength
RSpec.describe Users::Setting, type: :model do
describe "validations" do
subject { setting }
let(:setting) { create(:users_setting, user: user) }
let(:user) { create(:user) }
it { is_expected.to validate_length_of(:inbox_guidelines).is_at_most(250).allow_nil }
it { is_expected.to define_enum_for(:inbox_type).with_values(private: 0, open: 1).with_suffix(:inbox) }
it { is_expected.to define_enum_for(:config_font).with_values(default: 0, comic_sans: 1, monospace: 2, open_dyslexic: 3, sans_serif: 4, serif: 5) }
it { is_expected.to define_enum_for(:config_navbar).with_values(default_navbar: 0, static_navbar: 1) }
it { is_expected.to define_enum_for(:config_theme).with_values(default_theme: 0, minimal_light_theme: 1, night_theme: 2, pink_theme: 3, ten_x_hacker_theme: 4) }
describe "when validating feed_url", vcr: true do
it "is valid with no feed_url" do
setting.feed_url = nil
expect(setting).to be_valid
end
it "is not valid with an invalid feed_url", vcr: { cassette_name: "feeds_validate_url_invalid" } do
setting.feed_url = "http://example.com"
expect(setting).not_to be_valid
end
it "is valid with a valid feed_url", vcr: { cassette_name: "feeds_import_medium_vaidehi" } do
setting.feed_url = "https://medium.com/feed/@vaidehijoshi"
expect(setting).to be_valid
end
end
describe "#config_theme" do
it "accepts valid theme" do
setting.config_theme = 2
expect(setting).to be_valid
expect(setting.night_theme?).to be true
end
it "does not accept invalid theme" do
expect { setting.config_theme = 10 }.to raise_error(ArgumentError)
end
end
describe "#config_font" do
it "accepts valid font" do
setting.config_font = 4
expect(setting).to be_valid
expect(setting.sans_serif?).to be true
end
it "does not accept invalid font" do
expect { setting.config_font = 10 }.to raise_error(ArgumentError)
end
end
describe "#config_navbar" do
it "accepts valid navbar" do
setting.config_navbar = 1
expect(setting).to be_valid
expect(setting.static_navbar?).to be true
end
it "does not accept invalid navbar" do
expect { setting.config_navbar = 10 }.to raise_error(ArgumentError)
end
end
end
end
# rubocop:enable Layout/LineLength