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.
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