/
base.rb
179 lines (146 loc) · 4.99 KB
/
base.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# This class was adapted from the rails-settings-cached gem.
# See: https://github.com/huacnlee/rails-settings-cached
#
# It has changed in several significant ways:
# * renamed DSL method from "field" to "setting"
# * refactored request caching to allow for more than one settings model
# * removed features like cache prefixes and readonly fields
# * changed code in accordance with Rubocop and our internal practices
module Settings
class ProtectedKeyError < ArgumentError; end
class Base < ApplicationRecord
self.abstract_class = true
PROTECTED_KEYS = %w[var value].freeze
SEPARATOR_REGEXP = /[\n,;]+/
after_commit :clear_cache, on: %i[create update destroy]
class << self
def clear_cache
RequestStore.delete(cache_key)
Rails.cache.delete(cache_key)
end
def setting(key, default: nil, type: :string, separator: nil, validates: nil)
define_setting(
key,
default: default,
type: type,
separator: separator,
validates: validates,
)
end
def get_setting(key)
@defined_settings.detect { |setting| setting[:key] == key.to_s } || {}
end
def get_default(key)
get_setting(key)[:default]
end
def keys
@defined_settings.pluck(:key)
end
def to_h
keys.to_h { |k| [k.to_sym, public_send(k)] }
end
private
def cache_key
@cache_key ||= name.underscore
end
def define_setting(key, default: nil, type: :string, separator: nil, validates: nil)
key = key.to_s
raise(ProtectedKeyError, "Can't use '#{key}' as setting name") if key.in?(PROTECTED_KEYS)
@defined_settings ||= []
@defined_settings << {
key: key,
default: default,
type: type || :string
}
# Getter
define_singleton_method(key) do
result = __send__(:value_of, key)
if result.nil? # we don't want to accidentally do this for "false"
result ||= default.is_a?(Proc) ? default.call : default
end
read_as_type = type == :markdown ? :string : type
result = __send__(:convert_string_to_value_type, read_as_type, result, separator: separator)
result
end
# Setter
define_singleton_method("#{key}=") do |value|
var_name = key
record = find_by(var: var_name) || new(var: var_name)
if type == :markdown
processed = __send__(:convert_string_to_value_type, type, value)
record.value = value
record.save!
__send__(:"#{key}_processed_html=", processed)
else
value = __send__(:convert_string_to_value_type, type, value, separator: separator)
record.value = value
record.save!
end
value
end
# Validation
if validates
validates[:if] = proc { |item| item.var.to_s == key }
__send__(:validates, key, **validates)
define_method(:read_attribute_for_validation) { |_key| self.value }
end
return unless type == :boolean
# Predicate method for booleans
define_singleton_method("#{key}?") { __send__(key) }
end
def convert_string_to_value_type(type, value, separator: nil)
return value unless value.class.in?([String, Integer, Float, BigDecimal])
case type
when :boolean
value.in?(["true", "1", 1, true])
when :array
value.split(separator || SEPARATOR_REGEXP).compact_blank.map(&:strip)
when :hash
value = begin
YAML.safe_load(value).to_h
rescue StandardError
{}
end
value.deep_stringify_keys!
ActiveSupport::HashWithIndifferentAccess.new(value)
when :integer
value.to_i
when :float
value.to_f
when :big_decimal
value.to_d
when :markdown
ContentRenderer.new(value).process.processed_html
else
value
end
end
def value_of(var_name)
unless table_exists?
# Fallback to default value if table was not ready (before migrate)
Rails.logger.warn("'#{table_name}' does not exist, '#{name}.#{var_name}' will return the default value.")
return
end
all_settings[var_name]
end
def all_settings
RequestStore[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 1.week) do
unscoped.select(:var, :value).each_with_object({}) do |record, result|
result[record.var] = record.value
end.with_indifferent_access
end
end
end
# get the setting's value, YAML decoded
def value
YAML.unsafe_load(self[:value]) if self[:value].present?
end
# set the settings's value, YAML encoded
def value=(new_value)
self[:value] = new_value.to_yaml
end
def clear_cache
self.class.clear_cache
end
end
end