setting_accessors 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|