setting_accessors 0.0.1

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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +29 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +254 -0
  8. data/Rakefile +2 -0
  9. data/lib/generators/setting_accessors/install_generator.rb +38 -0
  10. data/lib/generators/setting_accessors/templates/migration.rb.erb +12 -0
  11. data/lib/generators/setting_accessors/templates/model.rb.erb +47 -0
  12. data/lib/generators/setting_accessors/templates/settings.yml +24 -0
  13. data/lib/setting_accessors.rb +26 -0
  14. data/lib/setting_accessors/accessor.rb +148 -0
  15. data/lib/setting_accessors/converter.rb +67 -0
  16. data/lib/setting_accessors/integration.rb +79 -0
  17. data/lib/setting_accessors/integration_validator.rb +15 -0
  18. data/lib/setting_accessors/internal.rb +108 -0
  19. data/lib/setting_accessors/setting_scaffold.rb +252 -0
  20. data/lib/setting_accessors/validator.rb +144 -0
  21. data/lib/setting_accessors/version.rb +3 -0
  22. data/lib/tasks/setting_accessors_tasks.rake +4 -0
  23. data/setting_accessors.gemspec +30 -0
  24. data/test/dummy/README.rdoc +28 -0
  25. data/test/dummy/Rakefile +6 -0
  26. data/test/dummy/app/assets/images/.keep +0 -0
  27. data/test/dummy/app/assets/javascripts/application.js +13 -0
  28. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  29. data/test/dummy/app/controllers/application_controller.rb +5 -0
  30. data/test/dummy/app/controllers/concerns/.keep +0 -0
  31. data/test/dummy/app/helpers/application_helper.rb +2 -0
  32. data/test/dummy/app/mailers/.keep +0 -0
  33. data/test/dummy/app/models/.keep +0 -0
  34. data/test/dummy/app/models/concerns/.keep +0 -0
  35. data/test/dummy/app/models/setting.rb +59 -0
  36. data/test/dummy/app/models/user.rb +15 -0
  37. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  38. data/test/dummy/bin/bundle +3 -0
  39. data/test/dummy/bin/rails +4 -0
  40. data/test/dummy/bin/rake +4 -0
  41. data/test/dummy/config.ru +4 -0
  42. data/test/dummy/config/application.rb +25 -0
  43. data/test/dummy/config/boot.rb +5 -0
  44. data/test/dummy/config/database.yml +25 -0
  45. data/test/dummy/config/environment.rb +5 -0
  46. data/test/dummy/config/environments/development.rb +37 -0
  47. data/test/dummy/config/environments/production.rb +83 -0
  48. data/test/dummy/config/environments/test.rb +34 -0
  49. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  50. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  51. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  52. data/test/dummy/config/initializers/inflections.rb +16 -0
  53. data/test/dummy/config/initializers/mime_types.rb +4 -0
  54. data/test/dummy/config/initializers/session_store.rb +3 -0
  55. data/test/dummy/config/initializers/setting_accessors.rb +1 -0
  56. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  57. data/test/dummy/config/locales/en.yml +23 -0
  58. data/test/dummy/config/routes.rb +56 -0
  59. data/test/dummy/config/secrets.yml +22 -0
  60. data/test/dummy/config/settings.yml +23 -0
  61. data/test/dummy/db/migrate/20150102112106_create_users.rb +9 -0
  62. data/test/dummy/db/migrate/20150102115329_create_settings.rb +12 -0
  63. data/test/dummy/db/schema.rb +32 -0
  64. data/test/dummy/db/test.sqlite3 +0 -0
  65. data/test/dummy/lib/assets/.keep +0 -0
  66. data/test/dummy/log/.keep +0 -0
  67. data/test/dummy/public/404.html +67 -0
  68. data/test/dummy/public/422.html +67 -0
  69. data/test/dummy/public/500.html +66 -0
  70. data/test/dummy/public/favicon.ico +0 -0
  71. data/test/dummy/test/models/setting_test.rb +131 -0
  72. data/test/dummy/test/models/user_test.rb +5 -0
  73. data/test/generators/install_generator_test.rb +15 -0
  74. data/test/setting_accessors_test.rb +4 -0
  75. data/test/test_helper.rb +23 -0
  76. metadata +270 -0
@@ -0,0 +1,252 @@
1
+ #
2
+ # Helper methods for the chosen setting model
3
+ # They are in this module to leave the end developer some room for
4
+ # his own methods in the setting model for his own methods.
5
+ #
6
+
7
+ module SettingAccessors::SettingScaffold
8
+
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+ base.validates_with SettingAccessors::Validator
12
+ base.serialize :value
13
+
14
+ base.validates :name,
15
+ :uniqueness => {:scope => [:assignable_type, :assignable_id]},
16
+ :presence => true
17
+ end
18
+
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)
36
+ end
37
+
38
+ alias_method :get, :[]
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
49
+ end
50
+ end
51
+
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
94
+ end
95
+ end
96
+
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)
108
+ end
109
+
110
+ #
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
121
+ #
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)
130
+ end
131
+
132
+ #
133
+ # @return [Object] the default value for the given setting
134
+ #
135
+ def get_default_value(name, assignable = nil)
136
+ self.new(:name => name, :assignable => assignable).default_value
137
+ end
138
+
139
+ #
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.
143
+ #
144
+ # @return [Setting, NilClass] The found setting or nil if not existing
145
+ #
146
+ def setting_record(name, assignable = nil)
147
+ self.find_by(:name => name.to_s, :assignable => assignable)
148
+ end
149
+
150
+ #
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.
156
+ #
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.get(:value) || []
163
+ 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']
198
+ end
199
+
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
233
+
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
244
+
245
+ #
246
+ # @see {SettingAccessors::Internal#setting_data} for more information
247
+ #
248
+ def data
249
+ SettingAccessors::Internal.setting_data(name, assignable)
250
+ end
251
+
252
+ end
@@ -0,0 +1,144 @@
1
+ class SettingAccessors::Validator < ActiveModel::Validator
2
+
3
+ def validate(record)
4
+ record.send(:validations).each do |key, requirement|
5
+ if key.to_s == 'custom'
6
+ Array(requirement).each do |validation|
7
+ run_custom_validation(record, validation)
8
+ end
9
+ elsif built_in_validation?(key)
10
+ send("validate_#{key}", record, requirement)
11
+ else
12
+ raise ArgumentError.new("The invalid validation '#{key}' was given in model '#{defining_model(record).to_s}'")
13
+ end
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def defining_model(record)
20
+ if SettingAccessors::Internal.globally_defined_setting?(record.name) || !record.assignable
21
+ SettingAccessors.setting_class
22
+ else
23
+ record.assignable.class
24
+ end
25
+ end
26
+
27
+ #
28
+ # Runs a custom validation method
29
+ # The method may either be a Proc or an instance method in +record+.+class+
30
+ #
31
+ def run_custom_validation(record, proc)
32
+ case proc
33
+ when Proc
34
+ proc.call(record)
35
+ when Symbol
36
+ if defining_model(record).respond_to?(proc)
37
+ defining_model(record).send(proc)
38
+ else
39
+ raise ArgumentError.new "The method '#{proc}' was set up as validation method in model '#{defining_model(record).name}', but doesn't exist."
40
+ end
41
+ else
42
+ raise ArgumentError.new "An invalid validations method was given ('#{proc}')"
43
+ end
44
+ end
45
+
46
+ #
47
+ # @return [TrueClass, FalseClass] +true+ if the given validation
48
+ # is a built-in one.
49
+ #
50
+ def built_in_validation?(validation_name)
51
+ private_methods.include?("validate_#{validation_name}".to_sym)
52
+ end
53
+
54
+ #
55
+ # Validates that the setting's value is given
56
+ # accepts :allow_blank and :allow_nil as options
57
+ #
58
+ def validate_presence(record, requirement)
59
+ return true unless requirement
60
+
61
+ if requirement.is_a?(Hash)
62
+ if requirement['allow_blank'] && !record.value.nil? ||
63
+ requirement['allow_nil'] && record.value.nil? ||
64
+ record.value.present?
65
+ true
66
+ else
67
+ add_error record, :blank
68
+ false
69
+ end
70
+ else
71
+ add_error_if record.value.nil? || record.value == '', record, :blank
72
+ end
73
+ end
74
+
75
+ #
76
+ # Validates numericality of the setting's value based on the options given
77
+ # in settings.yml
78
+ #
79
+ def validate_numericality(record, options)
80
+ #Test if the value is Numeric in any way (float or int)
81
+ add_error_if(!parse_value_as_numeric(record.value), record, :not_a_number) && return
82
+
83
+ #If the validation was set to check for integer values, do that as well
84
+ add_error_if(options['only_integer'] && !parse_value_as_fixnum(record.value), record, :not_an_integer)
85
+ end
86
+
87
+ #
88
+ # Validates whether the given value is a valid boolean
89
+ #
90
+ def validate_boolean(record, requirement)
91
+ add_error_if(requirement && parse_value_as_boolean(record.value).nil?, record, :not_a_boolean)
92
+ end
93
+
94
+ #----------------------------------------------------------------
95
+ # Helper Methods
96
+ #----------------------------------------------------------------
97
+
98
+ def add_error(record, validation, options = {})
99
+ record.errors.add :value, validation, options
100
+ end
101
+
102
+ def add_error_if(cond, record, validation, options = {})
103
+ add_error(record, validation, options) if cond
104
+ cond
105
+ end
106
+
107
+ #
108
+ # Borrowed from Rails' numericality validator
109
+ # @return [Float, NilClass] the given String value as a float or nil
110
+ # if the value was not a valid Numeric
111
+ #
112
+ def parse_value_as_numeric(raw_value)
113
+ Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
114
+ rescue ArgumentError, TypeError
115
+ nil
116
+ end
117
+
118
+ #
119
+ # Borrowed from Rails' numericality validator
120
+ #
121
+ # @return [Fixnum, NilClass] the given String value as an int or nil
122
+ #
123
+ def parse_value_as_fixnum(raw_value)
124
+ raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
125
+ end
126
+
127
+ #
128
+ # Tries to parse the given value as a boolean value
129
+ #
130
+ # @return [TrueClass, FalseClass, NilClass]
131
+ #
132
+ def parse_value_as_boolean(raw_value)
133
+ case raw_value
134
+ when TrueClass, FalseClass
135
+ raw_value
136
+ when String
137
+ return true if %w[true 1].include?(raw_value.downcase)
138
+ return false if %w[false 0].include?(raw_value.downcase)
139
+ nil
140
+ else
141
+ nil
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,3 @@
1
+ module SettingAccessors
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :setting_accessors do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'setting_accessors/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'setting_accessors'
8
+ spec.version = SettingAccessors::VERSION
9
+ spec.authors = ["Stefan Exner"]
10
+ spec.email = ["stex@sterex.de"]
11
+ spec.summary = %q{A global key-value-store and virtual model columns}
12
+ spec.description = %q{Adds a global key-value-store to Rails applications and allows adding typed columns
13
+ to model classes without having to change the database layout.}
14
+ spec.homepage = 'https://www.github.com/stex/setting_accessors'
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake", "~> 10.4"
24
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
25
+ spec.add_development_dependency 'shoulda', '~> 3.5'
26
+ spec.add_development_dependency 'minitest', '~> 5.5'
27
+ spec.add_development_dependency 'byebug', '~> 3.5'
28
+
29
+ spec.add_dependency 'rails', '~> 4'
30
+ end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.