Change user settings to be stored in a more optimal way (#23630)
Co-authored-by: Claire <claire.github-309c@sitedethib.com>local
parent
e7c3e55874
commit
a9b5598c97
36 changed files with 818 additions and 526 deletions
@ -1,155 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class UserSettingsDecorator |
||||
attr_reader :user, :settings |
||||
|
||||
def initialize(user) |
||||
@user = user |
||||
end |
||||
|
||||
def update(settings) |
||||
@settings = settings |
||||
process_update |
||||
end |
||||
|
||||
private |
||||
|
||||
def process_update |
||||
user.settings['notification_emails'] = merged_notification_emails if change?('notification_emails') |
||||
user.settings['interactions'] = merged_interactions if change?('interactions') |
||||
user.settings['default_privacy'] = default_privacy_preference if change?('setting_default_privacy') |
||||
user.settings['default_sensitive'] = default_sensitive_preference if change?('setting_default_sensitive') |
||||
user.settings['default_language'] = default_language_preference if change?('setting_default_language') |
||||
user.settings['unfollow_modal'] = unfollow_modal_preference if change?('setting_unfollow_modal') |
||||
user.settings['boost_modal'] = boost_modal_preference if change?('setting_boost_modal') |
||||
user.settings['delete_modal'] = delete_modal_preference if change?('setting_delete_modal') |
||||
user.settings['auto_play_gif'] = auto_play_gif_preference if change?('setting_auto_play_gif') |
||||
user.settings['display_media'] = display_media_preference if change?('setting_display_media') |
||||
user.settings['expand_spoilers'] = expand_spoilers_preference if change?('setting_expand_spoilers') |
||||
user.settings['reduce_motion'] = reduce_motion_preference if change?('setting_reduce_motion') |
||||
user.settings['disable_swiping'] = disable_swiping_preference if change?('setting_disable_swiping') |
||||
user.settings['system_font_ui'] = system_font_ui_preference if change?('setting_system_font_ui') |
||||
user.settings['noindex'] = noindex_preference if change?('setting_noindex') |
||||
user.settings['theme'] = theme_preference if change?('setting_theme') |
||||
user.settings['aggregate_reblogs'] = aggregate_reblogs_preference if change?('setting_aggregate_reblogs') |
||||
user.settings['show_application'] = show_application_preference if change?('setting_show_application') |
||||
user.settings['advanced_layout'] = advanced_layout_preference if change?('setting_advanced_layout') |
||||
user.settings['use_blurhash'] = use_blurhash_preference if change?('setting_use_blurhash') |
||||
user.settings['use_pending_items'] = use_pending_items_preference if change?('setting_use_pending_items') |
||||
user.settings['trends'] = trends_preference if change?('setting_trends') |
||||
user.settings['crop_images'] = crop_images_preference if change?('setting_crop_images') |
||||
user.settings['always_send_emails'] = always_send_emails_preference if change?('setting_always_send_emails') |
||||
end |
||||
|
||||
def merged_notification_emails |
||||
user.settings['notification_emails'].merge coerced_settings('notification_emails').to_h |
||||
end |
||||
|
||||
def merged_interactions |
||||
user.settings['interactions'].merge coerced_settings('interactions').to_h |
||||
end |
||||
|
||||
def default_privacy_preference |
||||
settings['setting_default_privacy'] |
||||
end |
||||
|
||||
def default_sensitive_preference |
||||
boolean_cast_setting 'setting_default_sensitive' |
||||
end |
||||
|
||||
def unfollow_modal_preference |
||||
boolean_cast_setting 'setting_unfollow_modal' |
||||
end |
||||
|
||||
def boost_modal_preference |
||||
boolean_cast_setting 'setting_boost_modal' |
||||
end |
||||
|
||||
def delete_modal_preference |
||||
boolean_cast_setting 'setting_delete_modal' |
||||
end |
||||
|
||||
def system_font_ui_preference |
||||
boolean_cast_setting 'setting_system_font_ui' |
||||
end |
||||
|
||||
def auto_play_gif_preference |
||||
boolean_cast_setting 'setting_auto_play_gif' |
||||
end |
||||
|
||||
def display_media_preference |
||||
settings['setting_display_media'] |
||||
end |
||||
|
||||
def expand_spoilers_preference |
||||
boolean_cast_setting 'setting_expand_spoilers' |
||||
end |
||||
|
||||
def reduce_motion_preference |
||||
boolean_cast_setting 'setting_reduce_motion' |
||||
end |
||||
|
||||
def disable_swiping_preference |
||||
boolean_cast_setting 'setting_disable_swiping' |
||||
end |
||||
|
||||
def noindex_preference |
||||
boolean_cast_setting 'setting_noindex' |
||||
end |
||||
|
||||
def show_application_preference |
||||
boolean_cast_setting 'setting_show_application' |
||||
end |
||||
|
||||
def theme_preference |
||||
settings['setting_theme'] |
||||
end |
||||
|
||||
def default_language_preference |
||||
settings['setting_default_language'] |
||||
end |
||||
|
||||
def aggregate_reblogs_preference |
||||
boolean_cast_setting 'setting_aggregate_reblogs' |
||||
end |
||||
|
||||
def advanced_layout_preference |
||||
boolean_cast_setting 'setting_advanced_layout' |
||||
end |
||||
|
||||
def use_blurhash_preference |
||||
boolean_cast_setting 'setting_use_blurhash' |
||||
end |
||||
|
||||
def use_pending_items_preference |
||||
boolean_cast_setting 'setting_use_pending_items' |
||||
end |
||||
|
||||
def trends_preference |
||||
boolean_cast_setting 'setting_trends' |
||||
end |
||||
|
||||
def crop_images_preference |
||||
boolean_cast_setting 'setting_crop_images' |
||||
end |
||||
|
||||
def always_send_emails_preference |
||||
boolean_cast_setting 'setting_always_send_emails' |
||||
end |
||||
|
||||
def boolean_cast_setting(key) |
||||
ActiveModel::Type::Boolean.new.cast(settings[key]) |
||||
end |
||||
|
||||
def coerced_settings(key) |
||||
coerce_values settings.fetch(key, {}) |
||||
end |
||||
|
||||
def coerce_values(params_hash) |
||||
params_hash.transform_values { |x| ActiveModel::Type::Boolean.new.cast(x) } |
||||
end |
||||
|
||||
def change?(key) |
||||
!settings[key].nil? |
||||
end |
||||
end |
@ -0,0 +1,19 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class UserSettingsSerializer |
||||
def self.load(value) |
||||
json = begin |
||||
if value.blank? |
||||
{} |
||||
else |
||||
Oj.load(value, symbol_keys: true) |
||||
end |
||||
end |
||||
|
||||
UserSettings.new(json) |
||||
end |
||||
|
||||
def self.dump(value) |
||||
Oj.dump(value.as_json) |
||||
end |
||||
end |
@ -0,0 +1,141 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
module HasUserSettings |
||||
extend ActiveSupport::Concern |
||||
|
||||
included do |
||||
serialize :settings, UserSettingsSerializer |
||||
end |
||||
|
||||
def settings_attributes=(attributes) |
||||
settings.update(attributes) |
||||
end |
||||
|
||||
def prefers_noindex? |
||||
settings['noindex'] |
||||
end |
||||
|
||||
def preferred_posting_language |
||||
valid_locale_cascade(settings['default_language'], locale, I18n.locale) |
||||
end |
||||
|
||||
def setting_auto_play_gif |
||||
settings['web.auto_play'] |
||||
end |
||||
|
||||
def setting_default_sensitive |
||||
settings['default_sensitive'] |
||||
end |
||||
|
||||
def setting_unfollow_modal |
||||
settings['web.unfollow_modal'] |
||||
end |
||||
|
||||
def setting_boost_modal |
||||
settings['web.reblog_modal'] |
||||
end |
||||
|
||||
def setting_delete_modal |
||||
settings['web.delete_modal'] |
||||
end |
||||
|
||||
def setting_reduce_motion |
||||
settings['web.reduce_motion'] |
||||
end |
||||
|
||||
def setting_system_font_ui |
||||
settings['web.use_system_font'] |
||||
end |
||||
|
||||
def setting_noindex |
||||
settings['noindex'] |
||||
end |
||||
|
||||
def setting_theme |
||||
settings['theme'] |
||||
end |
||||
|
||||
def setting_display_media |
||||
settings['web.display_media'] |
||||
end |
||||
|
||||
def setting_expand_spoilers |
||||
settings['web.expand_content_warnings'] |
||||
end |
||||
|
||||
def setting_default_language |
||||
settings['default_language'] |
||||
end |
||||
|
||||
def setting_aggregate_reblogs |
||||
settings['aggregate_reblogs'] |
||||
end |
||||
|
||||
def setting_show_application |
||||
settings['show_application'] |
||||
end |
||||
|
||||
def setting_advanced_layout |
||||
settings['web.advanced_layout'] |
||||
end |
||||
|
||||
def setting_use_blurhash |
||||
settings['web.use_blurhash'] |
||||
end |
||||
|
||||
def setting_use_pending_items |
||||
settings['web.use_pending_items'] |
||||
end |
||||
|
||||
def setting_trends |
||||
settings['web.trends'] |
||||
end |
||||
|
||||
def setting_crop_images |
||||
settings['web.crop_images'] |
||||
end |
||||
|
||||
def setting_disable_swiping |
||||
settings['web.disable_swiping'] |
||||
end |
||||
|
||||
def setting_always_send_emails |
||||
settings['always_send_emails'] |
||||
end |
||||
|
||||
def setting_default_privacy |
||||
settings['default_privacy'] || (account.locked? ? 'private' : 'public') |
||||
end |
||||
|
||||
def allows_report_emails? |
||||
settings['notification_emails.report'] |
||||
end |
||||
|
||||
def allows_pending_account_emails? |
||||
settings['notification_emails.pending_account'] |
||||
end |
||||
|
||||
def allows_appeal_emails? |
||||
settings['notification_emails.appeal'] |
||||
end |
||||
|
||||
def allows_trends_review_emails? |
||||
settings['notification_emails.trends'] |
||||
end |
||||
|
||||
def aggregates_reblogs? |
||||
settings['aggregate_reblogs'] |
||||
end |
||||
|
||||
def shows_application? |
||||
settings['show_application'] |
||||
end |
||||
|
||||
def show_all_media? |
||||
settings['web.display_media'] == 'show_all' |
||||
end |
||||
|
||||
def hide_all_media? |
||||
settings['web.display_media'] == 'hide_all' |
||||
end |
||||
end |
@ -0,0 +1,99 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class UserSettings |
||||
class Error < StandardError; end |
||||
class KeyError < Error; end |
||||
|
||||
include UserSettings::DSL |
||||
include UserSettings::Glue |
||||
|
||||
setting :always_send_emails, default: false |
||||
setting :aggregate_reblogs, default: true |
||||
setting :theme, default: -> { ::Setting.theme } |
||||
setting :noindex, default: -> { ::Setting.noindex } |
||||
setting :show_application, default: true |
||||
setting :default_language, default: nil |
||||
setting :default_sensitive, default: false |
||||
setting :default_privacy, default: nil |
||||
|
||||
namespace :web do |
||||
setting :crop_images, default: true |
||||
setting :advanced_layout, default: false |
||||
setting :trends, default: true |
||||
setting :use_blurhash, default: true |
||||
setting :use_pending_items, default: false |
||||
setting :use_system_font, default: false |
||||
setting :disable_swiping, default: false |
||||
setting :delete_modal, default: true |
||||
setting :reblog_modal, default: false |
||||
setting :unfollow_modal, default: true |
||||
setting :reduce_motion, default: false |
||||
setting :expand_content_warnings, default: false |
||||
setting :display_media, default: 'default', in: %w(default show_all hide_all) |
||||
setting :auto_play, default: false |
||||
end |
||||
|
||||
namespace :notification_emails do |
||||
setting :follow, default: true |
||||
setting :reblog, default: false |
||||
setting :favourite, default: false |
||||
setting :mention, default: true |
||||
setting :follow_request, default: true |
||||
setting :report, default: true |
||||
setting :pending_account, default: true |
||||
setting :trends, default: true |
||||
setting :appeal, default: true |
||||
end |
||||
|
||||
namespace :interactions do |
||||
setting :must_be_follower, default: false |
||||
setting :must_be_following, default: false |
||||
setting :must_be_following_dm, default: false |
||||
end |
||||
|
||||
def initialize(original_hash) |
||||
@original_hash = original_hash || {} |
||||
end |
||||
|
||||
def [](key) |
||||
key = key.to_sym |
||||
|
||||
raise KeyError, "Undefined setting: #{key}" unless self.class.definition_for?(key) |
||||
|
||||
if @original_hash.key?(key) |
||||
@original_hash[key] |
||||
else |
||||
self.class.definition_for(key).default_value |
||||
end |
||||
end |
||||
|
||||
def []=(key, value) |
||||
key = key.to_sym |
||||
|
||||
raise KeyError, "Undefined setting: #{key}" unless self.class.definition_for?(key) |
||||
|
||||
typecast_value = self.class.definition_for(key).type_cast(value) |
||||
|
||||
if typecast_value.nil? |
||||
@original_hash.delete(key) |
||||
else |
||||
@original_hash[key] = typecast_value |
||||
end |
||||
end |
||||
|
||||
def update(params) |
||||
params.each do |k, v| |
||||
self[k] = v unless v.nil? |
||||
end |
||||
end |
||||
|
||||
keys.each do |key| |
||||
define_method(key) do |
||||
self[key] |
||||
end |
||||
end |
||||
|
||||
def as_json |
||||
@original_hash |
||||
end |
||||
end |
@ -0,0 +1,37 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
module UserSettings::DSL |
||||
module ClassMethods |
||||
def setting(key, options = {}) |
||||
@definitions ||= {} |
||||
|
||||
UserSettings::Setting.new(key, options).tap do |s| |
||||
@definitions[s.key] = s |
||||
end |
||||
end |
||||
|
||||
def namespace(key, &block) |
||||
@definitions ||= {} |
||||
|
||||
UserSettings::Namespace.new(key).configure(&block).tap do |n| |
||||
@definitions.merge!(n.definitions) |
||||
end |
||||
end |
||||
|
||||
def keys |
||||
@definitions.keys |
||||
end |
||||
|
||||
def definition_for(key) |
||||
@definitions[key.to_sym] |
||||
end |
||||
|
||||
def definition_for?(key) |
||||
@definitions.key?(key.to_sym) |
||||
end |
||||
end |
||||
|
||||
def self.included(base) |
||||
base.extend ClassMethods |
||||
end |
||||
end |
@ -0,0 +1,23 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
module UserSettings::Glue |
||||
def to_model |
||||
self |
||||
end |
||||
|
||||
def to_key |
||||
'' |
||||
end |
||||
|
||||
def persisted? |
||||
false |
||||
end |
||||
|
||||
def type_for_attribute(key) |
||||
self.class.definition_for(key)&.type |
||||
end |
||||
|
||||
def has_attribute?(key) # rubocop:disable Naming/PredicateName |
||||
self.class.definition_for?(key) |
||||
end |
||||
end |
@ -0,0 +1,21 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class UserSettings::Namespace |
||||
attr_reader :name, :definitions |
||||
|
||||
def initialize(name) |
||||
@name = name.to_sym |
||||
@definitions = {} |
||||
end |
||||
|
||||
def configure(&block) |
||||
instance_eval(&block) |
||||
self |
||||
end |
||||
|
||||
def setting(key, options = {}) |
||||
UserSettings::Setting.new(key, options.merge(namespace: name)).tap do |s| |
||||
@definitions[s.key] = s |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,48 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class UserSettings::Setting |
||||
attr_reader :name, :namespace, :in |
||||
|
||||
def initialize(name, options = {}) |
||||
@name = name.to_sym |
||||
@default_value = options[:default] |
||||
@namespace = options[:namespace] |
||||
@in = options[:in] |
||||
end |
||||
|
||||
def default_value |
||||
if @default_value.respond_to?(:call) |
||||
@default_value.call |
||||
else |
||||
@default_value |
||||
end |
||||
end |
||||
|
||||
def type |
||||
if @default_value.is_a?(TrueClass) || @default_value.is_a?(FalseClass) |
||||
ActiveModel::Type::Boolean.new |
||||
else |
||||
ActiveModel::Type::String.new |
||||
end |
||||
end |
||||
|
||||
def type_cast(value) |
||||
if type.respond_to?(:cast) |
||||
type.cast(value) |
||||
else |
||||
value |
||||
end |
||||
end |
||||
|
||||
def to_a |
||||
[key, default_value] |
||||
end |
||||
|
||||
def key |
||||
if namespace |
||||
"#{namespace}.#{name}".to_sym |
||||
else |
||||
name |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,7 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class AddSettingsToUsers < ActiveRecord::Migration[6.1] |
||||
def change |
||||
add_column :users, :settings, :text |
||||
end |
||||
end |
@ -0,0 +1,84 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
class MoveUserSettings < ActiveRecord::Migration[6.1] |
||||
class User < ApplicationRecord; end |
||||
|
||||
MAPPING = { |
||||
default_privacy: 'default_privacy', |
||||
default_sensitive: 'web.default_sensitive', |
||||
default_language: 'default_language', |
||||
noindex: 'noindex', |
||||
theme: 'theme', |
||||
trends: 'web.trends', |
||||
unfollow_modal: 'web.unfollow_modal', |
||||
boost_modal: 'web.reblog_modal', |
||||
delete_modal: 'web.delete_modal', |
||||
auto_play_gif: 'web.auto_play', |
||||
display_media: 'web.display_media', |
||||
expand_spoilers: 'web.expand_content_warnings', |
||||
reduce_motion: 'web.reduce_motion', |
||||
disable_swiping: 'web.disable_swiping', |
||||
show_application: 'show_application', |
||||
system_font_ui: 'web.use_system_font', |
||||
aggregate_reblogs: 'aggregate_reblogs', |
||||
advanced_layout: 'web.advanced_layout', |
||||
use_blurhash: 'web.use_blurhash', |
||||
use_pending_items: 'web.use_pending_items', |
||||
crop_images: 'web.crop_images', |
||||
notification_emails: { |
||||
follow: 'notification_emails.follow', |
||||
reblog: 'notification_emails.reblog', |
||||
favourite: 'notification_emails.favourite', |
||||
mention: 'notification_emails.mention', |
||||
follow_request: 'notification_emails.follow_request', |
||||
report: 'notification_emails.report', |
||||
pending_account: 'notification_emails.pending_account', |
||||
trending_tag: 'notification_emails.trends', |
||||
appeal: 'notification_emails.appeal', |
||||
}.freeze, |
||||
always_send_emails: 'always_send_emails', |
||||
interactions: { |
||||
must_be_follower: 'interactions.must_be_follower', |
||||
must_be_following: 'interactions.must_be_following', |
||||
must_be_following_dm: 'interactions.must_be_following_dm', |
||||
}.freeze, |
||||
}.freeze |
||||
|
||||
class LegacySetting < ApplicationRecord |
||||
self.table_name = 'settings' |
||||
|
||||
def var |
||||
self[:var]&.to_sym |
||||
end |
||||
|
||||
def value |
||||
YAML.safe_load(self[:value], permitted_classes: [ActiveSupport::HashWithIndifferentAccess]) if self[:value].present? |
||||
end |
||||
end |
||||
|
||||
def up |
||||
User.find_each do |user| |
||||
previous_settings = LegacySetting.where(thing_type: 'User', thing_id: user.id).index_by(&:var) |
||||
|
||||
user_settings = {} |
||||
|
||||
MAPPING.each do |legacy_key, new_key| |
||||
value = previous_settings[legacy_key]&.value |
||||
|
||||
next if value.blank? |
||||
|
||||
if value.is_a?(Hash) |
||||
value.each do |nested_key, nested_value| |
||||
user_settings[MAPPING[legacy_key][nested_key.to_sym]] = nested_value |
||||
end |
||||
else |
||||
user_settings[new_key] = value |
||||
end |
||||
end |
||||
|
||||
user.update_column('settings', Oj.dump(user_settings)) # rubocop:disable Rails/SkipsModelValidations |
||||
end |
||||
end |
||||
|
||||
def down; end |
||||
end |
@ -1,16 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe Settings::Extend do |
||||
class User |
||||
include Settings::Extend |
||||
end |
||||
|
||||
describe '#settings' do |
||||
it 'sets @settings as an instance of Settings::ScopedSettings' do |
||||
user = Fabricate(:user) |
||||
expect(user.settings).to be_a Settings::ScopedSettings |
||||
end |
||||
end |
||||
end |
@ -1,35 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe Settings::ScopedSettings do |
||||
let(:object) { Fabricate(:user) } |
||||
let(:scoped_setting) { described_class.new(object) } |
||||
let(:val) { 'whatever' } |
||||
let(:methods) { %i(auto_play_gif default_sensitive unfollow_modal boost_modal delete_modal reduce_motion system_font_ui noindex theme) } |
||||
|
||||
describe '.initialize' do |
||||
it 'sets @object' do |
||||
scoped_setting = described_class.new(object) |
||||
expect(scoped_setting.instance_variable_get(:@object)).to be object |
||||
end |
||||
end |
||||
|
||||
describe '#method_missing' do |
||||
it 'sets scoped_setting.method_name = val' do |
||||
methods.each do |key| |
||||
scoped_setting.send("#{key}=", val) |
||||
expect(scoped_setting.send(key)).to eq val |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '#[]= and #[]' do |
||||
it 'sets [key] = val' do |
||||
methods.each do |key| |
||||
scoped_setting[key] = val |
||||
expect(scoped_setting[key]).to eq val |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,84 +0,0 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
describe UserSettingsDecorator do |
||||
describe 'update' do |
||||
let(:user) { Fabricate(:user) } |
||||
let(:settings) { described_class.new(user) } |
||||
|
||||
it 'updates the user settings value for email notifications' do |
||||
values = { 'notification_emails' => { 'follow' => '1' } } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['notification_emails']['follow']).to be true |
||||
end |
||||
|
||||
it 'updates the user settings value for interactions' do |
||||
values = { 'interactions' => { 'must_be_follower' => '0' } } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['interactions']['must_be_follower']).to be false |
||||
end |
||||
|
||||
it 'updates the user settings value for privacy' do |
||||
values = { 'setting_default_privacy' => 'public' } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['default_privacy']).to eq 'public' |
||||
end |
||||
|
||||
it 'updates the user settings value for sensitive' do |
||||
values = { 'setting_default_sensitive' => '1' } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['default_sensitive']).to be true |
||||
end |
||||
|
||||
it 'updates the user settings value for unfollow modal' do |
||||
values = { 'setting_unfollow_modal' => '0' } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['unfollow_modal']).to be false |
||||
end |
||||
|
||||
it 'updates the user settings value for boost modal' do |
||||
values = { 'setting_boost_modal' => '1' } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['boost_modal']).to be true |
||||
end |
||||
|
||||
it 'updates the user settings value for delete toot modal' do |
||||
values = { 'setting_delete_modal' => '0' } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['delete_modal']).to be false |
||||
end |
||||
|
||||
it 'updates the user settings value for gif auto play' do |
||||
values = { 'setting_auto_play_gif' => '0' } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['auto_play_gif']).to be false |
||||
end |
||||
|
||||
it 'updates the user settings value for system font in UI' do |
||||
values = { 'setting_system_font_ui' => '0' } |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['system_font_ui']).to be false |
||||
end |
||||
|
||||
it 'decoerces setting values before applying' do |
||||
values = { |
||||
'setting_delete_modal' => 'false', |
||||
'setting_boost_modal' => 'true', |
||||
} |
||||
|
||||
settings.update(values) |
||||
expect(user.settings['delete_modal']).to be false |
||||
expect(user.settings['boost_modal']).to be true |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,25 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe UserSettings::Namespace do |
||||
subject { described_class.new(name) } |
||||
|
||||
let(:name) { :foo } |
||||
|
||||
describe '#setting' do |
||||
before do |
||||
subject.setting :bar, default: 'baz' |
||||
end |
||||
|
||||
it 'adds setting to definitions' do |
||||
expect(subject.definitions[:'foo.bar']).to have_attributes(name: :bar, namespace: :foo, default_value: 'baz') |
||||
end |
||||
end |
||||
|
||||
describe '#definitions' do |
||||
it 'returns a hash' do |
||||
expect(subject.definitions).to be_a Hash |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,74 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe UserSettings::Setting do |
||||
subject { described_class.new(name, options) } |
||||
|
||||
let(:name) { :foo } |
||||
let(:options) { { default: default, namespace: namespace } } |
||||
let(:default) { false } |
||||
let(:namespace) { nil } |
||||
|
||||
describe '#default_value' do |
||||
context 'when default value is a primitive value' do |
||||
it 'returns default value' do |
||||
expect(subject.default_value).to eq default |
||||
end |
||||
end |
||||
|
||||
context 'when default value is a proc' do |
||||
let(:default) { -> { 'bar' } } |
||||
|
||||
it 'returns value from proc' do |
||||
expect(subject.default_value).to eq 'bar' |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '#type' do |
||||
it 'returns a type' do |
||||
expect(subject.type).to be_a ActiveModel::Type::Value |
||||
end |
||||
end |
||||
|
||||
describe '#type_cast' do |
||||
context 'when default value is a boolean' do |
||||
let(:default) { false } |
||||
|
||||
it 'returns boolean' do |
||||
expect(subject.type_cast('1')).to be true |
||||
end |
||||
end |
||||
|
||||
context 'when default value is a string' do |
||||
let(:default) { '' } |
||||
|
||||
it 'returns string' do |
||||
expect(subject.type_cast(1)).to eq '1' |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '#to_a' do |
||||
it 'returns an array' do |
||||
expect(subject.to_a).to eq [name, default] |
||||
end |
||||
end |
||||
|
||||
describe '#key' do |
||||
context 'when there is no namespace' do |
||||
it 'returnsn a symbol' do |
||||
expect(subject.key).to eq :foo |
||||
end |
||||
end |
||||
|
||||
context 'when there is a namespace' do |
||||
let(:namespace) { :bar } |
||||
|
||||
it 'returns a symbol' do |
||||
expect(subject.key).to eq :'bar.foo' |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,110 @@ |
||||
# frozen_string_literal: true |
||||
|
||||
require 'rails_helper' |
||||
|
||||
RSpec.describe UserSettings do |
||||
subject { described_class.new(json) } |
||||
|
||||
let(:json) { {} } |
||||
|
||||
describe '#[]' do |
||||
context 'when setting is not set' do |
||||
it 'returns default value' do |
||||
expect(subject[:always_send_emails]).to be false |
||||
end |
||||
end |
||||
|
||||
context 'when setting is set' do |
||||
let(:json) { { default_language: 'fr' } } |
||||
|
||||
it 'returns value' do |
||||
expect(subject[:default_language]).to eq 'fr' |
||||
end |
||||
end |
||||
|
||||
context 'when setting was not defined' do |
||||
it 'raises error' do |
||||
expect { subject[:foo] }.to raise_error UserSettings::KeyError |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '#[]=' do |
||||
context 'when value matches type' do |
||||
before do |
||||
subject[:always_send_emails] = true |
||||
end |
||||
|
||||
it 'updates value' do |
||||
expect(subject[:always_send_emails]).to be true |
||||
end |
||||
end |
||||
|
||||
context 'when value needs to be type-cast' do |
||||
before do |
||||
subject[:always_send_emails] = '1' |
||||
end |
||||
|
||||
it 'updates value with a type-cast' do |
||||
expect(subject[:always_send_emails]).to be true |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '#update' do |
||||
before do |
||||
subject.update(always_send_emails: true, default_language: 'fr', default_privacy: nil) |
||||
end |
||||
|
||||
it 'updates values' do |
||||
expect(subject[:always_send_emails]).to be true |
||||
expect(subject[:default_language]).to eq 'fr' |
||||
end |
||||
|
||||
it 'does not set values that are nil' do |
||||
expect(subject.as_json).to_not include(default_privacy: nil) |
||||
end |
||||
end |
||||
|
||||
describe '#as_json' do |
||||
let(:json) { { default_language: 'fr' } } |
||||
|
||||
it 'returns hash' do |
||||
expect(subject.as_json).to eq json |
||||
end |
||||
end |
||||
|
||||
describe '.keys' do |
||||
it 'returns an array' do |
||||
expect(described_class.keys).to be_a Array |
||||
end |
||||
end |
||||
|
||||
describe '.definition_for' do |
||||
context 'when key is defined' do |
||||
it 'returns a setting' do |
||||
expect(described_class.definition_for(:always_send_emails)).to be_a UserSettings::Setting |
||||
end |
||||
end |
||||
|
||||
context 'when key is not defined' do |
||||
it 'returns nil' do |
||||
expect(described_class.definition_for(:foo)).to be_nil |
||||
end |
||||
end |
||||
end |
||||
|
||||
describe '.definition_for?' do |
||||
context 'when key is defined' do |
||||
it 'returns true' do |
||||
expect(described_class.definition_for?(:always_send_emails)).to be true |
||||
end |
||||
end |
||||
|
||||
context 'when key is not defined' do |
||||
it 'returns false' do |
||||
expect(described_class.definition_for?(:foo)).to be false |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue