setting_accessors 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.editorconfig +10 -0
- data/.rspec +1 -0
- data/.rubocop.yml +131 -0
- data/.travis.yml +11 -7
- data/Appraisals +17 -0
- data/CHANGELOG.md +38 -1
- data/Gemfile +2 -0
- data/README.md +64 -124
- data/Rakefile +5 -27
- data/bin/console +15 -0
- data/bin/setup +10 -0
- data/gemfiles/rails_4.1.gemfile +7 -0
- data/gemfiles/rails_4.2.gemfile +7 -0
- data/gemfiles/rails_4.2.gemfile.lock +162 -0
- data/gemfiles/rails_5.0.gemfile +7 -0
- data/gemfiles/rails_5.0.gemfile.lock +169 -0
- data/gemfiles/rails_5.1.gemfile +7 -0
- data/gemfiles/rails_5.1.gemfile.lock +169 -0
- data/gemfiles/rails_5.2.gemfile +7 -0
- data/gemfiles/rails_5.2.gemfile.lock +177 -0
- data/lib/generators/setting_accessors/install_generator.rb +11 -9
- data/lib/generators/setting_accessors/templates/model.rb.erb +0 -24
- data/lib/setting_accessors.rb +14 -5
- data/lib/setting_accessors/accessor_generator.rb +66 -0
- data/lib/setting_accessors/converters/base.rb +24 -0
- data/lib/setting_accessors/converters/boolean_converter.rb +51 -0
- data/lib/setting_accessors/converters/integer_converter.rb +21 -0
- data/lib/setting_accessors/converters/polymorphic_converter.rb +11 -0
- data/lib/setting_accessors/converters/string_converter.rb +11 -0
- data/lib/setting_accessors/helpers.rb +28 -0
- data/lib/setting_accessors/integration.rb +83 -97
- data/lib/setting_accessors/internal.rb +37 -64
- data/lib/setting_accessors/setting_scaffold.rb +147 -214
- data/lib/setting_accessors/setting_set.rb +168 -0
- data/lib/setting_accessors/version.rb +3 -1
- data/lib/tasks/setting_accessors_tasks.rake +2 -0
- data/setting_accessors.gemspec +27 -19
- metadata +117 -143
- data/.codeclimate.yml +0 -66
- data/lib/setting_accessors/accessor.rb +0 -189
- data/lib/setting_accessors/converter.rb +0 -71
- data/lib/setting_accessors/integration_validator.rb +0 -15
- data/lib/setting_accessors/validator.rb +0 -144
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/images/.keep +0 -0
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/controllers/concerns/.keep +0 -0
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/mailers/.keep +0 -0
- data/test/dummy/app/models/.keep +0 -0
- data/test/dummy/app/models/concerns/.keep +0 -0
- data/test/dummy/app/models/post.rb +0 -2
- data/test/dummy/app/models/setting.rb +0 -59
- data/test/dummy/app/models/user.rb +0 -19
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -25
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/database.yml +0 -25
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -37
- data/test/dummy/config/environments/production.rb +0 -83
- data/test/dummy/config/environments/test.rb +0 -34
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -4
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/setting_accessors.rb +0 -1
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/config/secrets.yml +0 -22
- data/test/dummy/config/settings.yml +0 -23
- data/test/dummy/db/migrate/20150102112106_create_users.rb +0 -9
- data/test/dummy/db/migrate/20150102115329_create_settings.rb +0 -12
- data/test/dummy/db/migrate/20150723114600_create_posts.rb +0 -11
- data/test/dummy/db/schema.rb +0 -40
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/lib/assets/.keep +0 -0
- data/test/dummy/public/404.html +0 -67
- data/test/dummy/public/422.html +0 -67
- data/test/dummy/public/500.html +0 -66
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/fixtures/posts.yml +0 -11
- data/test/dummy/test/models/post_test.rb +0 -19
- data/test/dummy/test/models/setting_test.rb +0 -143
- data/test/dummy/test/models/user_test.rb +0 -154
- data/test/generators/install_generator_test.rb +0 -15
- data/test/setting_accessors_test.rb +0 -4
- data/test/test_helper.rb +0 -31
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SettingAccessors
|
4
|
+
module Helpers
|
5
|
+
class Error < StandardError; end
|
6
|
+
class NestedHashKeyNotFoundException < Error; end
|
7
|
+
|
8
|
+
def ensure_nested_hash!(hash, *keys)
|
9
|
+
h = hash
|
10
|
+
keys.each do |key|
|
11
|
+
h[key] ||= {}
|
12
|
+
h = h[key]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def lookup_nested_hash(hash, *keys)
|
17
|
+
fail NestedHashKeyNotFoundException if hash.nil?
|
18
|
+
|
19
|
+
h = hash
|
20
|
+
keys.each do |key|
|
21
|
+
fail NestedHashKeyNotFoundException unless h.key?(key)
|
22
|
+
|
23
|
+
h = h[key]
|
24
|
+
end
|
25
|
+
h
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,117 +1,103 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SettingAccessors
|
4
|
+
module Integration
|
5
|
+
def self.included(base)
|
6
|
+
# After the main record was saved, we can save its settings.
|
7
|
+
# This is necessary as the record might have been a new record
|
8
|
+
# without an ID yet
|
9
|
+
base.after_save do
|
10
|
+
settings.send(:persist!)
|
11
|
+
|
12
|
+
# From AR 5.1 on, #_update actually checks whether the changed "attributes" are actually
|
13
|
+
# table columns or not. If no actual changed columns were found, the record is not changed and
|
14
|
+
# only the after_* callbacks are executed.
|
15
|
+
# This means that the settings are persisted, but the record's +updated_at+ column is not updated.
|
16
|
+
#
|
17
|
+
# This workaround triggers a #touch on the record in case no actual column change already
|
18
|
+
# triggered a timestamp update.
|
19
|
+
#
|
20
|
+
# TODO: This might lead to #after_commit being triggered twice, once by #update_* and once by #touch
|
21
|
+
touch if @_setting_accessors_touch_assignable
|
22
|
+
@_setting_accessors_touch_assignable = false
|
23
|
+
end
|
4
24
|
|
5
|
-
|
6
|
-
# This is necessary as the record might have been a new record
|
7
|
-
# without an ID yet
|
8
|
-
base.after_save do
|
9
|
-
settings.send(:persist!)
|
25
|
+
base.extend ClassMethods
|
10
26
|
end
|
11
27
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
28
|
+
module ClassMethods
|
29
|
+
|
30
|
+
#
|
31
|
+
# Generates a new accessor (=getter and setter) for the given setting
|
32
|
+
#
|
33
|
+
# @param [String, Symbol] setting_name
|
34
|
+
# The setting's name
|
35
|
+
#
|
36
|
+
# @param [Hash] options
|
37
|
+
# Options to customize the behaviour of the generated accessor
|
38
|
+
#
|
39
|
+
def setting_accessor(setting_name, **options)
|
40
|
+
generator = AccessorGenerator.new(setting_name, **options)
|
41
|
+
generator.assign_setting!(self)
|
42
|
+
end
|
43
|
+
end
|
16
44
|
|
17
45
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# The setting's name
|
46
|
+
# Previously read setting values have to be refreshed if a record is reloaded.
|
47
|
+
# Without this, #reload'ing a record would not update its setting values to the
|
48
|
+
# latest database version if they were previously read.
|
22
49
|
#
|
23
|
-
#
|
24
|
-
#
|
50
|
+
# Example to demonstrate the problem with this override:
|
51
|
+
# user = User.create(:a_boolean => true)
|
52
|
+
# user_alias = User.find(user.id)
|
53
|
+
# user.a_boolean = !user_alias.a_boolean
|
54
|
+
# user.save
|
55
|
+
# user_alias.reload
|
56
|
+
# user_alias.a_boolean
|
57
|
+
# #=> true
|
25
58
|
#
|
26
|
-
|
27
|
-
|
28
|
-
|
59
|
+
def reload(*)
|
60
|
+
super.tap { @settings = nil }
|
61
|
+
end
|
62
|
+
|
29
63
|
#
|
30
|
-
#
|
31
|
-
#
|
64
|
+
# Adds changed settings to ActiveModel's list of changed attributes.
|
65
|
+
# This is necessary for #changed? to work correctly without actually overriding
|
66
|
+
# the method itself.
|
32
67
|
#
|
33
|
-
#
|
68
|
+
# TODO: Check if it makes more sense to hook into AR5's AttributeMutationTracker instead
|
34
69
|
#
|
35
|
-
#
|
36
|
-
# setting and return +nil+ if none was specified previously.
|
70
|
+
# @return [Hash] All changed attributes
|
37
71
|
#
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
SettingAccessors::Internal.set_class_setting(self, setting_name, options)
|
42
|
-
|
43
|
-
setting_type = SettingAccessors::Internal.setting_value_type(setting_name, self).to_sym
|
44
|
-
|
45
|
-
#Add the setting's name to the list of setting_accessors for this class
|
46
|
-
SettingAccessors::Internal.add_setting_accessor_name(self, setting_name)
|
47
|
-
|
48
|
-
# Getter
|
49
|
-
define_method(setting_name) do
|
50
|
-
settings.get_with_fallback(setting_name, fallback)
|
51
|
-
end
|
52
|
-
|
53
|
-
# Getter alias for boolean settings
|
54
|
-
alias_method "#{setting_name}?", setting_name if setting_type == :boolean
|
55
|
-
|
56
|
-
# Setter
|
57
|
-
define_method("#{setting_name}=") do |new_value|
|
58
|
-
settings[setting_name] = new_value
|
59
|
-
end
|
60
|
-
|
61
|
-
#NAME_was
|
62
|
-
define_method("#{setting_name}_was") do
|
63
|
-
settings.value_was(setting_name, fallback)
|
64
|
-
end
|
65
|
-
|
66
|
-
#NAME_before_type_cast
|
67
|
-
define_method("#{setting_name}_before_type_cast") do
|
68
|
-
settings.value_before_type_cast(setting_name)
|
69
|
-
end
|
72
|
+
def changed_attributes
|
73
|
+
super.merge(settings.changed_settings)
|
74
|
+
end
|
70
75
|
|
71
|
-
|
72
|
-
|
73
|
-
|
76
|
+
#
|
77
|
+
# Marks the record to be #touch'd after updating it in case no actual
|
78
|
+
# attributes were involved and only settings were changed.
|
79
|
+
# This is necessary for ActiveRecord >= 5.1 which will not perform the
|
80
|
+
# timestamp updates if it thinks nothing actually changed.
|
81
|
+
#
|
82
|
+
if Gem.loaded_specs['activerecord'].version >= Gem::Version.create('5.1')
|
83
|
+
def _update_record(*)
|
84
|
+
super.tap do |affected_rows|
|
85
|
+
# Workaround to trigger a #touch if necessary, see +after_save+ callback further up
|
86
|
+
@_setting_accessors_touch_assignable = affected_rows.zero?
|
87
|
+
end
|
74
88
|
end
|
75
89
|
end
|
76
|
-
end
|
77
|
-
|
78
|
-
#
|
79
|
-
# Previously read setting values have to be refreshed if a record is reloaded.
|
80
|
-
# Without this, #reload'ing a record would not update its setting values to the
|
81
|
-
# latest database version if they were previously read.
|
82
|
-
#
|
83
|
-
# Example to demonstrate the problem with this override:
|
84
|
-
# user = User.create(:a_boolean => true)
|
85
|
-
# user_alias = User.find(user.id)
|
86
|
-
# user.a_boolean = !user_alias.a_boolean
|
87
|
-
# user.save
|
88
|
-
# user_alias.reload
|
89
|
-
# user_alias.a_boolean
|
90
|
-
# #=> true
|
91
|
-
#
|
92
|
-
def reload(*)
|
93
|
-
super
|
94
|
-
@settings_accessor = nil
|
95
|
-
self
|
96
|
-
end
|
97
|
-
|
98
|
-
def as_json(options = {})
|
99
|
-
json = super options
|
100
90
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
91
|
+
def as_json(options = {})
|
92
|
+
super.tap do |json|
|
93
|
+
SettingAccessors::Internal.json_setting_names(self.class, **options).each do |setting_name|
|
94
|
+
json[setting_name.to_s] = send(setting_name)
|
95
|
+
end
|
96
|
+
end
|
106
97
|
end
|
107
98
|
|
108
|
-
|
109
|
-
|
99
|
+
def settings
|
100
|
+
@settings ||= SettingAccessors::SettingSet.new(self)
|
110
101
|
end
|
111
|
-
json
|
112
|
-
end
|
113
|
-
|
114
|
-
def settings
|
115
|
-
@settings_accessor ||= SettingAccessors::Accessor.new(self)
|
116
102
|
end
|
117
103
|
end
|
@@ -1,48 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#
|
2
4
|
# This module contains class methods used internally.
|
3
5
|
#
|
4
6
|
module SettingAccessors
|
5
7
|
module Internal
|
8
|
+
extend Helpers
|
6
9
|
|
7
|
-
def self.
|
8
|
-
|
9
|
-
keys.each do |key|
|
10
|
-
h[key] ||= {}
|
11
|
-
h = h[key]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.lookup_nested_hash(hash, *keys)
|
16
|
-
return nil if hash.nil?
|
17
|
-
|
18
|
-
h = hash
|
19
|
-
keys.each do |key|
|
20
|
-
return nil if h[key].nil?
|
21
|
-
h = h[key]
|
22
|
-
end
|
23
|
-
h
|
24
|
-
end
|
25
|
-
|
26
|
-
#
|
27
|
-
# Loads information about all settings from YAML file
|
28
|
-
# These are cached in the class so they don't have to be reloaded
|
29
|
-
# every time.
|
30
|
-
#
|
31
|
-
# Note: For development / test, this is flushed every time
|
32
|
-
#
|
33
|
-
def self.global_config
|
34
|
-
if Rails.env.test? || Rails.env.development?
|
35
|
-
(YAML.load(File.open(Rails.root.join('config/settings.yml'))) || {}).deep_stringify_keys
|
36
|
-
else
|
37
|
-
@@config ||= (YAML.load(File.open(Rails.root.join('config/settings.yml'))) || {}).deep_stringify_keys
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
#
|
42
|
-
# @return [TrueClass, FalseClass] +true+ if the setting is defined in config/settings.yml
|
43
|
-
#
|
44
|
-
def self.globally_defined_setting?(setting_name)
|
45
|
-
self.global_config[setting_name.to_s].present?
|
10
|
+
def self.class_settings
|
11
|
+
@@class_settings ||= {}
|
46
12
|
end
|
47
13
|
|
48
14
|
#
|
@@ -52,20 +18,14 @@ module SettingAccessors
|
|
52
18
|
# by using setting_accessor in your model class
|
53
19
|
#
|
54
20
|
def self.set_class_setting(klass, setting_name, options = {})
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
raise ArgumentError.new("The setting #{setting_name} is already defined in config/settings.yml and may not be redefined in #{klass}")
|
64
|
-
|
65
|
-
#If the setting is defined on class base, we have to store its options
|
66
|
-
elsif options.any? && !self.globally_defined_setting?(setting_name)
|
67
|
-
self.ensure_nested_hash!(@@class_settings, klass.to_s)
|
68
|
-
@@class_settings[klass.to_s][setting_name.to_s] = options.deep_stringify_keys
|
21
|
+
# If there are no options given, the setting *has* to be defined globally.
|
22
|
+
if options.empty?
|
23
|
+
raise ArgumentError, "The setting '#{setting_name}' in model '#{klass}' is lacking options."
|
24
|
+
# If the setting is defined on class base, we have to store its options
|
25
|
+
else
|
26
|
+
ensure_nested_hash!(class_settings, klass.to_s)
|
27
|
+
class_settings[klass.to_s][setting_name.to_s] = options.deep_stringify_keys
|
28
|
+
add_setting_accessor_name(klass, setting_name)
|
69
29
|
end
|
70
30
|
end
|
71
31
|
|
@@ -80,32 +40,30 @@ module SettingAccessors
|
|
80
40
|
# As a convenience function (and to keep the existing code working),
|
81
41
|
# it is possible to provide a class or an instance of said class
|
82
42
|
assignable_class &&= assignable_class.class unless assignable_class.is_a?(Class)
|
83
|
-
|
84
|
-
(assignable_class && self.get_class_setting(assignable_class, setting_name)) ||
|
85
|
-
self.global_config[setting_name.to_s] ||
|
86
|
-
{}
|
43
|
+
(assignable_class && get_class_setting(assignable_class, setting_name)) || {}
|
87
44
|
end
|
88
45
|
|
89
46
|
#
|
90
47
|
# @return [String] the given setting's value type
|
91
48
|
#
|
92
49
|
def self.setting_value_type(*args)
|
93
|
-
|
50
|
+
setting_data(*args)['type'] || 'polymorphic'
|
94
51
|
end
|
95
52
|
|
96
53
|
#
|
97
54
|
# @return [SettingAccessors::Converter] A value converter for the given type
|
98
55
|
#
|
99
56
|
def self.converter(value_type)
|
100
|
-
|
101
|
-
@@converters[value_type.to_sym] ||= SettingAccessors::Converter.new(value_type)
|
57
|
+
Converters.const_get("#{value_type.to_s.camelize}Converter")
|
102
58
|
end
|
103
59
|
|
104
60
|
#
|
105
|
-
# @return [Hash,
|
61
|
+
# @return [Hash, nil] Information about a class specific setting or +nil+ if it wasn't set before
|
106
62
|
#
|
107
63
|
def self.get_class_setting(klass, setting_name)
|
108
|
-
|
64
|
+
lookup_nested_hash(class_settings, klass.to_s, setting_name.to_s)
|
65
|
+
rescue ::SettingAccessors::Helpers::NestedHashKeyNotFoundException
|
66
|
+
nil
|
109
67
|
end
|
110
68
|
|
111
69
|
#
|
@@ -124,8 +82,23 @@ module SettingAccessors
|
|
124
82
|
#
|
125
83
|
def self.setting_accessor_names(klass)
|
126
84
|
@@setting_accessor_names ||= {}
|
127
|
-
|
85
|
+
lookup_nested_hash(@@setting_accessor_names, klass.to_s) || []
|
128
86
|
end
|
129
87
|
|
88
|
+
#
|
89
|
+
# Mainly a helper for #as_json calls.
|
90
|
+
# Evaluates the given options and determines the setting names to be returned.
|
91
|
+
#
|
92
|
+
def self.json_setting_names(klass, **options)
|
93
|
+
setting_names = setting_accessor_names(klass)
|
94
|
+
|
95
|
+
if options[:only]
|
96
|
+
setting_names & Array(options[:only]).map(&:to_s)
|
97
|
+
elsif options[:except]
|
98
|
+
setting_names - Array(options[:except]).map(&:to_s)
|
99
|
+
else
|
100
|
+
setting_names
|
101
|
+
end
|
102
|
+
end
|
130
103
|
end
|
131
|
-
end
|
104
|
+
end
|
@@ -1,252 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#
|
2
4
|
# Helper methods for the chosen setting model
|
3
5
|
# They are in this module to leave the end developer some room for
|
4
6
|
# his own methods in the setting model for his own methods.
|
5
7
|
#
|
6
8
|
|
7
|
-
module SettingAccessors
|
8
|
-
|
9
|
-
def self.included(base)
|
10
|
-
base.extend ClassMethods
|
11
|
-
base.validates_with SettingAccessors::Validator
|
12
|
-
base.serialize :value
|
9
|
+
module SettingAccessors
|
10
|
+
module SettingScaffold
|
13
11
|
|
14
|
-
base
|
15
|
-
|
16
|
-
|
17
|
-
end
|
12
|
+
def self.included(base)
|
13
|
+
base.extend ClassMethods
|
14
|
+
base.serialize :value
|
18
15
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
#
|
23
|
-
# @param [String, Symbol] name
|
24
|
-
# The setting's name
|
25
|
-
#
|
26
|
-
# @param [ActiveRecord::Base] assignable
|
27
|
-
# If given, the setting searched has to be assigned to the given record
|
28
|
-
# If not given, a global setting is searched
|
29
|
-
#
|
30
|
-
# @return [Object, NilClass]
|
31
|
-
# If a setting is found, **its value** is returned.
|
32
|
-
# If not, +nil+ is returned.
|
33
|
-
#
|
34
|
-
def [](name, assignable = nil)
|
35
|
-
self.setting_record(name, assignable).try(:value)
|
16
|
+
base.validates :name,
|
17
|
+
uniqueness: {scope: [:assignable_type, :assignable_id]},
|
18
|
+
presence: true
|
36
19
|
end
|
37
20
|
|
38
|
-
|
21
|
+
module ClassMethods
|
22
|
+
#
|
23
|
+
# Searches for a setting in the database and returns its value
|
24
|
+
#
|
25
|
+
# @param [String, Symbol] name
|
26
|
+
# The setting's name
|
27
|
+
#
|
28
|
+
# @param [ActiveRecord::Base] assignable
|
29
|
+
# If given, the setting searched has to be assigned to the given record
|
30
|
+
# If not given, a global setting is searched
|
31
|
+
#
|
32
|
+
# @return [Object, NilClass]
|
33
|
+
# If a setting is found, **its value** is returned.
|
34
|
+
# If not, +nil+ is returned.
|
35
|
+
#
|
36
|
+
def get(name, assignable = nil)
|
37
|
+
setting_record(name, assignable).try(:value)
|
38
|
+
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
40
|
+
alias [] get
|
41
|
+
|
42
|
+
#
|
43
|
+
# Tries to look the setting up using #get, if no existing setting is found,
|
44
|
+
# the setting's default value is returned.
|
45
|
+
#
|
46
|
+
# This only works for class-wise settings, meaning that an assignable has to be present.
|
47
|
+
#
|
48
|
+
def get_or_default(name, assignable)
|
49
|
+
if (val = get(name, assignable)).nil?
|
50
|
+
new(name: name, assignable: assignable).default_value
|
51
|
+
else
|
52
|
+
val
|
53
|
+
end
|
49
54
|
end
|
50
|
-
end
|
51
55
|
|
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
|
-
# errors are noticed by the programmer / user.
|
78
|
-
# As this is usually only the case when coming from method_missing,
|
79
|
-
# it should not happen anyways
|
80
|
-
#
|
81
|
-
# @toto: Bless the rains down in Africa!
|
82
|
-
#
|
83
|
-
def create_or_update(name, value, assignable = nil, return_value = false)
|
84
|
-
setting = self.setting_record(name, assignable)
|
85
|
-
setting ||= self.new(:name => name, :assignable => assignable)
|
86
|
-
setting.set_value(value)
|
87
|
-
|
88
|
-
if return_value
|
89
|
-
setting.save!
|
90
|
-
setting.value
|
91
|
-
else
|
92
|
-
setting.save
|
93
|
-
setting
|
56
|
+
#
|
57
|
+
# Creates or updates the setting with the given name
|
58
|
+
#
|
59
|
+
# @param [String, Symbol] name
|
60
|
+
# The setting's name
|
61
|
+
#
|
62
|
+
# @param [Object] value
|
63
|
+
# The new setting value
|
64
|
+
#
|
65
|
+
# @param [ActiveRecord::Base] assignable
|
66
|
+
# The optional record this setting belongs to. If not
|
67
|
+
# given, the setting is global.
|
68
|
+
#
|
69
|
+
# @param [Boolean] return_value
|
70
|
+
# If set to +true+, only the setting's value is returned
|
71
|
+
#
|
72
|
+
# @return [Object] The newly set value
|
73
|
+
#
|
74
|
+
# @toto: Bless the rains down in Africa!
|
75
|
+
#
|
76
|
+
def set(name, value, assignable: nil)
|
77
|
+
(setting_record(name, assignable) || new(name: name, assignable: assignable)).tap do |setting|
|
78
|
+
setting.raw_value = value
|
79
|
+
setting.save
|
80
|
+
end.value
|
94
81
|
end
|
95
|
-
end
|
96
82
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
83
|
+
#
|
84
|
+
# An alias for #set with a slightly different API.
|
85
|
+
# This allows the following usage:
|
86
|
+
# Setting['my_setting', my_assignable] ||= new_value
|
87
|
+
#
|
88
|
+
def []=(name, *args)
|
89
|
+
assignable = args.size > 1 ? args.first : nil
|
90
|
+
set(name, args.last, assignable: assignable)
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# @return [Object] the default value for the given setting
|
95
|
+
#
|
96
|
+
def get_default_value(name, assignable = nil)
|
97
|
+
new(name: name, assignable: assignable).default_value
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Looks up a setting record for the given name and assignable
|
102
|
+
# Unlike the other methods here, this one actually returns a Setting object
|
103
|
+
# instead of its value.
|
104
|
+
#
|
105
|
+
# @return [Setting, NilClass] The found setting or nil if not existing
|
106
|
+
#
|
107
|
+
def setting_record(name, assignable = nil)
|
108
|
+
find_by(name: name.to_s, assignable: assignable)
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Makes accessing settings a little easier.
|
113
|
+
# Examples:
|
114
|
+
#
|
115
|
+
# #Loading **the value** of a global setting named "my_setting"
|
116
|
+
# Setting.my_setting
|
117
|
+
#
|
118
|
+
# #Setting **the value** of a global setting named "my_setting"
|
119
|
+
# Setting.my_setting = [1,2,3,4,5]
|
120
|
+
#
|
121
|
+
# #Loading **the value** of an assigned setting named "cool_setting"
|
122
|
+
# #+some_cool_user+ is here an instance of ActiveRecord::Base
|
123
|
+
# Setting.cool_setting(some_cool_user)
|
124
|
+
#
|
125
|
+
def method_missing(method, *args, &block)
|
126
|
+
method_name = method.to_s
|
127
|
+
|
128
|
+
if method_name.last == '='
|
129
|
+
return set(method_name[0..-2], args.first)
|
130
|
+
elsif args.size <= 1
|
131
|
+
return get(method_name, args.first)
|
132
|
+
end
|
133
|
+
|
134
|
+
super
|
135
|
+
end
|
108
136
|
end
|
109
137
|
|
110
138
|
#
|
111
|
-
#
|
112
|
-
# using the setting's default value stored in the config file
|
113
|
-
#
|
114
|
-
# If the setting already exists, its value will not be overridden
|
115
|
-
#
|
116
|
-
# @param [String] name
|
117
|
-
# The setting's name
|
118
|
-
#
|
119
|
-
# @param [ActiveRecord::Base] assignable
|
120
|
-
# An optional assignable
|
139
|
+
# @return [Object] the default value for the current setting
|
121
140
|
#
|
122
|
-
|
123
|
-
|
124
|
-
#
|
125
|
-
# @example Create a default setting for all users in the system
|
126
|
-
# User.all.each { |u| Setting.create_default_setting(:some_setting, u) }
|
127
|
-
#
|
128
|
-
def create_default_setting(name, assignable = nil)
|
129
|
-
self.create_or_update(name, self.get_or_default(name, assignable), assignable)
|
141
|
+
def default_value
|
142
|
+
data['default'].freeze
|
130
143
|
end
|
131
144
|
|
132
145
|
#
|
133
|
-
# @return [
|
146
|
+
# @return [String] the setting's type as specified in settings.yml
|
147
|
+
# If the setting wasn't specified, a polymorphic type is assumed
|
134
148
|
#
|
135
|
-
def
|
136
|
-
|
149
|
+
def value_type
|
150
|
+
data['type'] || 'polymorphic'
|
137
151
|
end
|
138
152
|
|
139
153
|
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
# instead of its value.
|
154
|
+
# @return [Object] the setting's value before it was type casted using the defined rule in settings.yml
|
155
|
+
# See #value_before_type_cast for ActiveRecord attributes
|
143
156
|
#
|
144
|
-
#
|
157
|
+
# We can't use the name #value_before_type_cast here as it would
|
158
|
+
# shadow ActiveRecord's default one - which might still be needed.
|
145
159
|
#
|
146
|
-
def
|
147
|
-
|
160
|
+
def raw_value
|
161
|
+
@raw_value || value
|
148
162
|
end
|
149
163
|
|
150
164
|
#
|
151
|
-
#
|
152
|
-
# setting name. This is done in this class method due to
|
153
|
-
# the process of setting creation through assigned records
|
154
|
-
# which does not allow going the "normal" way of testing whether
|
155
|
-
# a setting was saved correctly or not.
|
165
|
+
# Sets the new setting value by converting the raw value automatically.
|
156
166
|
#
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
s = self.new(:name => name, :value => value, :assignable => assignable)
|
161
|
-
s.valid?
|
162
|
-
s.errors[:value] || []
|
167
|
+
def raw_value=(new_value)
|
168
|
+
@raw_value = new_value
|
169
|
+
self.value = converter.new(new_value).convert
|
163
170
|
end
|
164
|
-
end
|
165
|
-
|
166
|
-
#
|
167
|
-
# @return [String] the localized setting name
|
168
|
-
# they are stored in config/locales/settings.LOCALE.yml
|
169
|
-
#
|
170
|
-
def localized_name
|
171
|
-
i18n_lookup(:name)
|
172
|
-
end
|
173
|
-
|
174
|
-
#
|
175
|
-
# @return [String] the localized setting description
|
176
|
-
# see #localized_name
|
177
|
-
#
|
178
|
-
def localized_description
|
179
|
-
i18n_lookup(:description)
|
180
|
-
end
|
181
|
-
|
182
|
-
#
|
183
|
-
# Performs an I18n lookup in the settings locale.
|
184
|
-
# Class based settings are store in 'settings.CLASS.NAME', globally defined settings
|
185
|
-
# in 'settings.global.NAME'
|
186
|
-
#
|
187
|
-
def i18n_lookup(key, options = {})
|
188
|
-
options[:scope] = [:settings, :global, self.name]
|
189
|
-
options[:scope] = [:settings, self.assignable.class.to_s.underscore, self.name] unless SettingAccessors::Internal.globally_defined_setting?(self.name)
|
190
|
-
I18n.t(key, options)
|
191
|
-
end
|
192
|
-
|
193
|
-
#
|
194
|
-
# @return [Object] the default value for the current setting
|
195
|
-
#
|
196
|
-
def default_value
|
197
|
-
data['default'].freeze
|
198
|
-
end
|
199
171
|
|
200
|
-
|
201
|
-
# @return [String] the setting's type as specified in settings.yml
|
202
|
-
# If the setting wasn't specified, a polymorphic type is assumed
|
203
|
-
#
|
204
|
-
def value_type
|
205
|
-
data['type'] || 'polymorphic'
|
206
|
-
end
|
207
|
-
|
208
|
-
#
|
209
|
-
# @return [Object] the setting's value before it was type casted using the defined rule in settings.yml
|
210
|
-
# See #value_before_type_cast for ActiveRecord attributes
|
211
|
-
#
|
212
|
-
# We can't use the name #value_before_type_cast here as it would
|
213
|
-
# shadow ActiveRecord's default one - which might still be needed.
|
214
|
-
#
|
215
|
-
def original_value
|
216
|
-
@original_value || self.value
|
217
|
-
end
|
218
|
-
|
219
|
-
#
|
220
|
-
# Sets the setting's value to the given one
|
221
|
-
# Performs automatic type casts
|
222
|
-
#
|
223
|
-
def set_value(new_value)
|
224
|
-
@original_value = new_value
|
225
|
-
self.value = converter.convert(new_value)
|
226
|
-
end
|
227
|
-
|
228
|
-
private
|
229
|
-
|
230
|
-
def converter
|
231
|
-
@converter ||= SettingAccessors::Internal.converter(value_type)
|
232
|
-
end
|
172
|
+
private
|
233
173
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
#
|
239
|
-
# Accessor to the validations part of the setting's data
|
240
|
-
#
|
241
|
-
def validations
|
242
|
-
data['validates'] || {}
|
243
|
-
end
|
174
|
+
def converter
|
175
|
+
@converter ||= SettingAccessors::Internal.converter(value_type)
|
176
|
+
end
|
244
177
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
178
|
+
#
|
179
|
+
# @see {SettingAccessors::Internal#setting_data} for more information
|
180
|
+
#
|
181
|
+
def data
|
182
|
+
SettingAccessors::Internal.setting_data(name, assignable)
|
183
|
+
end
|
250
184
|
end
|
251
|
-
|
252
185
|
end
|