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.
Files changed (99) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +10 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +131 -0
  5. data/.travis.yml +11 -7
  6. data/Appraisals +17 -0
  7. data/CHANGELOG.md +38 -1
  8. data/Gemfile +2 -0
  9. data/README.md +64 -124
  10. data/Rakefile +5 -27
  11. data/bin/console +15 -0
  12. data/bin/setup +10 -0
  13. data/gemfiles/rails_4.1.gemfile +7 -0
  14. data/gemfiles/rails_4.2.gemfile +7 -0
  15. data/gemfiles/rails_4.2.gemfile.lock +162 -0
  16. data/gemfiles/rails_5.0.gemfile +7 -0
  17. data/gemfiles/rails_5.0.gemfile.lock +169 -0
  18. data/gemfiles/rails_5.1.gemfile +7 -0
  19. data/gemfiles/rails_5.1.gemfile.lock +169 -0
  20. data/gemfiles/rails_5.2.gemfile +7 -0
  21. data/gemfiles/rails_5.2.gemfile.lock +177 -0
  22. data/lib/generators/setting_accessors/install_generator.rb +11 -9
  23. data/lib/generators/setting_accessors/templates/model.rb.erb +0 -24
  24. data/lib/setting_accessors.rb +14 -5
  25. data/lib/setting_accessors/accessor_generator.rb +66 -0
  26. data/lib/setting_accessors/converters/base.rb +24 -0
  27. data/lib/setting_accessors/converters/boolean_converter.rb +51 -0
  28. data/lib/setting_accessors/converters/integer_converter.rb +21 -0
  29. data/lib/setting_accessors/converters/polymorphic_converter.rb +11 -0
  30. data/lib/setting_accessors/converters/string_converter.rb +11 -0
  31. data/lib/setting_accessors/helpers.rb +28 -0
  32. data/lib/setting_accessors/integration.rb +83 -97
  33. data/lib/setting_accessors/internal.rb +37 -64
  34. data/lib/setting_accessors/setting_scaffold.rb +147 -214
  35. data/lib/setting_accessors/setting_set.rb +168 -0
  36. data/lib/setting_accessors/version.rb +3 -1
  37. data/lib/tasks/setting_accessors_tasks.rake +2 -0
  38. data/setting_accessors.gemspec +27 -19
  39. metadata +117 -143
  40. data/.codeclimate.yml +0 -66
  41. data/lib/setting_accessors/accessor.rb +0 -189
  42. data/lib/setting_accessors/converter.rb +0 -71
  43. data/lib/setting_accessors/integration_validator.rb +0 -15
  44. data/lib/setting_accessors/validator.rb +0 -144
  45. data/test/dummy/README.rdoc +0 -28
  46. data/test/dummy/Rakefile +0 -6
  47. data/test/dummy/app/assets/images/.keep +0 -0
  48. data/test/dummy/app/assets/javascripts/application.js +0 -13
  49. data/test/dummy/app/assets/stylesheets/application.css +0 -15
  50. data/test/dummy/app/controllers/application_controller.rb +0 -5
  51. data/test/dummy/app/controllers/concerns/.keep +0 -0
  52. data/test/dummy/app/helpers/application_helper.rb +0 -2
  53. data/test/dummy/app/mailers/.keep +0 -0
  54. data/test/dummy/app/models/.keep +0 -0
  55. data/test/dummy/app/models/concerns/.keep +0 -0
  56. data/test/dummy/app/models/post.rb +0 -2
  57. data/test/dummy/app/models/setting.rb +0 -59
  58. data/test/dummy/app/models/user.rb +0 -19
  59. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  60. data/test/dummy/bin/bundle +0 -3
  61. data/test/dummy/bin/rails +0 -4
  62. data/test/dummy/bin/rake +0 -4
  63. data/test/dummy/config.ru +0 -4
  64. data/test/dummy/config/application.rb +0 -25
  65. data/test/dummy/config/boot.rb +0 -5
  66. data/test/dummy/config/database.yml +0 -25
  67. data/test/dummy/config/environment.rb +0 -5
  68. data/test/dummy/config/environments/development.rb +0 -37
  69. data/test/dummy/config/environments/production.rb +0 -83
  70. data/test/dummy/config/environments/test.rb +0 -34
  71. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  72. data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
  73. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  74. data/test/dummy/config/initializers/inflections.rb +0 -16
  75. data/test/dummy/config/initializers/mime_types.rb +0 -4
  76. data/test/dummy/config/initializers/session_store.rb +0 -3
  77. data/test/dummy/config/initializers/setting_accessors.rb +0 -1
  78. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  79. data/test/dummy/config/locales/en.yml +0 -23
  80. data/test/dummy/config/routes.rb +0 -56
  81. data/test/dummy/config/secrets.yml +0 -22
  82. data/test/dummy/config/settings.yml +0 -23
  83. data/test/dummy/db/migrate/20150102112106_create_users.rb +0 -9
  84. data/test/dummy/db/migrate/20150102115329_create_settings.rb +0 -12
  85. data/test/dummy/db/migrate/20150723114600_create_posts.rb +0 -11
  86. data/test/dummy/db/schema.rb +0 -40
  87. data/test/dummy/db/test.sqlite3 +0 -0
  88. data/test/dummy/lib/assets/.keep +0 -0
  89. data/test/dummy/public/404.html +0 -67
  90. data/test/dummy/public/422.html +0 -67
  91. data/test/dummy/public/500.html +0 -66
  92. data/test/dummy/public/favicon.ico +0 -0
  93. data/test/dummy/test/fixtures/posts.yml +0 -11
  94. data/test/dummy/test/models/post_test.rb +0 -19
  95. data/test/dummy/test/models/setting_test.rb +0 -143
  96. data/test/dummy/test/models/user_test.rb +0 -154
  97. data/test/generators/install_generator_test.rb +0 -15
  98. data/test/setting_accessors_test.rb +0 -4
  99. data/test/test_helper.rb +0 -31
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SettingAccessors
4
+ module Converters
5
+ class PolymorphicConverter < Base
6
+ def parse_value
7
+ value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SettingAccessors
4
+ module Converters
5
+ class StringConverter < Base
6
+ def parse_value
7
+ value.to_s
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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
- module SettingAccessors::Integration
2
- def self.included(base)
3
- base.validates_with SettingAccessors::IntegrationValidator
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
- # After the main record was saved, we can save its settings.
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
- base.extend ClassMethods
13
- end
14
-
15
- module ClassMethods
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
- # Generates a new accessor (=getter and setter) for the given setting
19
- #
20
- # @param [String, Symbol] setting_name
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
- # @param [Hash] options
24
- # Options to customize the behaviour of the generated accessor
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
- # @option options [Symbol, Object] :fallback (nil)
27
- # If set to +:default+, the getter will return the setting's default
28
- # value if no own value was specified for this record
59
+ def reload(*)
60
+ super.tap { @settings = nil }
61
+ end
62
+
29
63
  #
30
- # If set to +:global+, the getter will try to find a global
31
- # setting if no record specific setting was found
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
- # If set to another value, this value is used by default
68
+ # TODO: Check if it makes more sense to hook into AR5's AttributeMutationTracker instead
34
69
  #
35
- # If not set at all or to +nil+, the getter will only search for a record specific
36
- # setting and return +nil+ if none was specified previously.
70
+ # @return [Hash] All changed attributes
37
71
  #
38
- def setting_accessor(setting_name, options = {})
39
- fallback = options.delete(:fallback)
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
- #NAME_changed?
72
- define_method("#{setting_name}_changed?") do
73
- settings.value_changed?(setting_name)
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
- setting_names = SettingAccessors::Internal.setting_accessor_names(self.class)
102
- if only = options[:only]
103
- setting_names &= Array(only).map(&:to_s)
104
- elsif except = options[:except]
105
- setting_names -= Array(except).map(&:to_s)
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
- setting_names.each do |setting_name|
109
- json[setting_name.to_s] = send(setting_name)
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.ensure_nested_hash!(hash, *keys)
8
- h = hash
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
- @@class_settings ||= {}
56
-
57
- #If there are no options given, the setting *has* to be defined globally.
58
- if options.empty? && !self.globally_defined_setting?(setting_name)
59
- raise ArgumentError.new "The setting '#{setting_name}' in model '#{klass.to_s}' is neither globally defined nor did it receive options"
60
-
61
- #A setting which is already globally defined, may not be re-defined on class base
62
- elsif self.globally_defined_setting?(setting_name) && options.any?
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
- self.setting_data(*args)['type'] || 'polymorphic'
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
- @@converters ||= {}
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, NilClass] Information about a class specific setting or +nil+ if it wasn't set before
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
- self.lookup_nested_hash(@@class_settings, klass.to_s, setting_name.to_s)
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
- self.lookup_nested_hash(@@setting_accessor_names, klass.to_s) || []
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::SettingScaffold
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.validates :name,
15
- :uniqueness => {:scope => [:assignable_type, :assignable_id]},
16
- :presence => true
17
- end
12
+ def self.included(base)
13
+ base.extend ClassMethods
14
+ base.serialize :value
18
15
 
19
- module ClassMethods
20
- #
21
- # Searches for a setting in the database and returns its value
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
- alias_method :get, :[]
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
- # Tries to look the setting up using #get, if no existing setting is found,
42
- # the setting's default value is returned.
43
- #
44
- def get_or_default(name, assignable = nil)
45
- if (val = self[name, assignable]).nil?
46
- self.new(:name => name, :assignable => assignable).default_value
47
- else
48
- val
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
- # Creates or updates the setting with the given name
54
- #
55
- # @param [String, Symbol] name
56
- # The setting's name
57
- #
58
- # @param [Object] value
59
- # The new setting value
60
- #
61
- # @param [ActiveRecord::Base] assignable
62
- # The optional record this setting belongs to. If not
63
- # given, the setting is global.
64
- #
65
- # @param [Boolean] return_value
66
- # If set to +true+, only the setting's value is returned
67
- #
68
- # @return [Object, Setting]
69
- # Depending on +return_value+ either the newly created Setting record
70
- # or the newly assigned value.
71
- # This is due to the fact that Setting.my_setting = 'something' should
72
- # show the same behaviour as other attribute assigns in the system while
73
- # you might still want to get validation errors on custom settings.
74
- #
75
- # Please note that - if +return_value+ is set to +true+,
76
- # #save! is used instead of #save to ensure that validation
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
- # Shortcut for #create_or_update
99
- #
100
- # @param [String, Symbol] name
101
- # The setting's name
102
- #
103
- # The second argument is an optional assignable
104
- #
105
- def []=(name, *args)
106
- assignable = args.size > 1 ? args.first : nil
107
- self.create_or_update(name, args.last, assignable, true)
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
- # Creates a new setting for the given name and assignable,
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
- # @example Create a global default setting 'meaning_of_life'
123
- # Setting.create_default_setting(:meaning_of_life)
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 [Object] the default value for the given setting
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 get_default_value(name, assignable = nil)
136
- self.new(:name => name, :assignable => assignable).default_value
149
+ def value_type
150
+ data['type'] || 'polymorphic'
137
151
  end
138
152
 
139
153
  #
140
- # Looks up a setting record for the given name and assignable
141
- # Unlike the other methods here, this one actually returns a Setting object
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
- # @return [Setting, NilClass] The found setting or nil if not existing
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 setting_record(name, assignable = nil)
147
- self.find_by(:name => name.to_s, :assignable => assignable)
160
+ def raw_value
161
+ @raw_value || value
148
162
  end
149
163
 
150
164
  #
151
- # Tests, if the given value would be valid for the given
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
- # @return [Array<String>] The validation errors for the setting's value
158
- #
159
- def validation_errors(name, value, assignable = nil)
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
- def value_required?
235
- !!validations['required']
236
- end
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
- # @see {SettingAccessors::Internal#setting_data} for more information
247
- #
248
- def data
249
- SettingAccessors::Internal.setting_data(name, assignable)
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