Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
# @example
# class User < ApplicationRecord
# include HasSetting
# has_setting :setting, :user_setting_item
# end
#
# # == Schema Information
# #
# # Table name: user_settings
# #
# # id :integer unsigned, not null, primary key
# # user_id :integer unsigned, not null
# # user_setting_item_id :integer unsigned, not null
# # flag :boolean default(FALSE), not null
# #
# # Indexes
# #
# # ux_user_id_user_setting_item_id (user_id,user_setting_item_id) UNIQUE
# #
# class UserSettting < ApplicationRecord
# belongs_to :user_setting_item
# delegate :name, to: :user_setting_item
# end
#
# # == Schema Information
# #
# # Table name: user_setting_items
# #
# # id :integer unsigned, not null, primary key
# # name :string(255) not null
# #
# class UserSetttingItem < ApplicationRecord
# end
#
# UserSetttingItem.create!(name: 'foo')
# UserSetttingItem.create!(name: 'bar')
#
# user = User.first
# user.setting.enabled #=> []
# user.setting.update!(foo: true, bar: true)
# user.setting.enabled #=> ["foo", "bar"]
# user.setting.enabled?(:foo) #=> true
# user.setting.disable(:foo)
# user.setting.enabled?(:foo) #=> false
# user.setting.save
# user.setting.save!
module HasSetting
extend ActiveSupport::Concern
included do
class_attribute :__assoc_to_setting_name_assoc
end
class_methods do
# @param assoc [Symbol] association name for the setting
# @param setting_name_assoc [Symbol] association name of the setting name
# @param has_many_opts [Hash] options for +has_many+
def has_setting(assoc, setting_name_assoc, **has_many_opts)
self.__assoc_to_setting_name_assoc ||= {}
self.__assoc_to_setting_name_assoc[assoc] = setting_name_assoc
has_many assoc, -> { includes(setting_name_assoc).extending(Extension) }, has_many_opts
end
end
module Extension
def enabled?(name)
!!find_setting(name)&.flag
end
def enabled
each.with_object([]) do |setting, names|
next unless setting.flag
names << setting.name
end
end
def enable(*names)
with_setting_cache do
names.each do |name|
find_or_initialize(name).flag = true
end
end
end
def disable(*names)
names.each do |name|
find_setting(name)&.flag=(false)
end
end
def save
transaction do
all? { |setting| setting.save }
end
end
def save!
transaction do
each { |setting| setting.save! }
end
end
def update!(settings)
with_setting_cache do
settings.each do |name, flag|
if flag
enable(name)
else
disable(name)
end
end
end
save!
end
private
def setting_name_refrectoin
setting_name_assoc = proxy_association.owner.__assoc_to_setting_name_assoc[proxy_association.reflection.name]
proxy_association.klass.reflect_on_association(setting_name_assoc)
end
def name_to_id(name)
name_to_id = @__setting_cache&.[](:name_to_id) || setting_name_refrectoin.klass.pluck(:name, :id).to_h
@__setting_cache[:name_to_id] = name_to_id if @__setting_cache
id = name_to_id[name.to_s]
raise ArgumentError, "Unknown item: #{name}" if id.nil?
id
end
def with_setting_cache
@__with_setting_cache_depth ||= 0
@__with_setting_cache_depth += 1
@__setting_cache = {}
yield
ensure
@__with_setting_cache_depth -= 1
remove_instance_variable(:@__setting_cache) if @__with_setting_cache_depth.zero?
end
def find_setting(name)
find { |setting| setting.name == name.to_s }
end
def find_or_initialize(name)
find_setting(name) || proxy_association.build(setting_name_refrectoin.foreign_key => name_to_id(name))
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.