setting_accessors 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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>.