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
data/.codeclimate.yml DELETED
@@ -1,66 +0,0 @@
1
- ---
2
- engines:
3
- rubocop:
4
- enabled: true
5
- eslint:
6
- enabled: true
7
- csslint:
8
- enabled: true
9
- ratings:
10
- paths:
11
- - "**.rb"
12
- - "**.js"
13
- - "**.jsx"
14
- - "**.css"
15
- exclude_paths:
16
- - test/**/*
17
- # This is a sample .codeclimate.yml configured for Engine analysis on Code
18
- # Climate Platform. For an overview of the Code Climate Platform, see here:
19
- # http://docs.codeclimate.com/article/300-the-codeclimate-platform
20
-
21
- # Under the engines key, you can configure which engines will analyze your repo.
22
- # Each key is an engine name. For each value, you need to specify enabled: true
23
- # to enable the engine as well as any other engines-specific configuration.
24
-
25
- # For more details, see here:
26
- # http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform
27
-
28
- # For a list of all available engines, see here:
29
- # http://docs.codeclimate.com/article/296-engines-available-engines
30
-
31
- engines:
32
- # to turn on an engine, add it here and set enabled to `true`
33
- # to turn off an engine, set enabled to `false` or remove it
34
- rubocop:
35
- enabled: true
36
- golint:
37
- enabled: true
38
- gofmt:
39
- enabled: true
40
- eslint:
41
- enabled: true
42
- csslint:
43
- enabled: true
44
-
45
- # Engines can analyze files and report issues on them, but you can separately
46
- # decide which files will receive ratings based on those issues. This is
47
- # specified by path patterns under the ratings key.
48
-
49
- # For more details see here:
50
- # http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform
51
-
52
- # Note: If the ratings key is not specified, this will result in a 0.0 GPA on your dashboard.
53
-
54
- # ratings:
55
- # paths:
56
- # - app/**
57
- # - lib/**
58
- # - "**.rb"
59
- # - "**.go"
60
-
61
- # You can globally exclude files from being analyzed by any engine using the
62
- # exclude_paths key.
63
-
64
- #exclude_paths:
65
- #- spec/**/*
66
- #- vendor/**/*
@@ -1,189 +0,0 @@
1
- #
2
- # Helper class to make accessing record specific settings easier
3
- #
4
- class SettingAccessors::Accessor
5
-
6
- def initialize(record)
7
- @record = record
8
- @temp_settings = {}
9
- end
10
-
11
- #
12
- # Tries to retrieve the given setting's value from the temp settings
13
- # (already read/written values in this instance). If the setting hasn't been
14
- # used before, its value is retrieved from the database.
15
- #
16
- # If a setting hasn't been read by this record (instance) before, its value
17
- # is stored in the local read set.
18
- #
19
- # TODO: See if this causes problems with read settings not being updated by external changes.
20
- # User1: Read Setting X
21
- # User2: Update Setting X
22
- # User1: Read Setting X -> Gets old value from temp settings.
23
- # This shouldn't be too dangerous as the system state will be refreshed with every request though.
24
- #
25
- def [](key)
26
- return @temp_settings[key.to_sym] if has_key?(key)
27
- value = SettingAccessors.setting_class.get(key, @record)
28
- @temp_settings[key.to_sym] = value unless value.nil?
29
- value
30
- end
31
-
32
- #
33
- # Tries to fetch a setting value using the provided key and #[].
34
- # It will only return the +default+ value if there is
35
- # - no temporary setting with the given key AND
36
- # - no already persisted setting (see #[])
37
- #
38
- def fetch(key, default = nil)
39
- result = self[key]
40
- return default if result.nil? && !has_key?(key)
41
- result
42
- end
43
-
44
- #
45
- # Like #fetch, but it will store the default value as a temporary setting
46
- # if no actual setting value could be found. This is useful to further work
47
- # with default setting values.
48
- # The default value is cloned (using #dup to avoid copying object states) before
49
- # it is assigned. This will not work for singleton instances like true, false, etc.
50
- #
51
- def fetch_and_store(key, default = nil)
52
- result = self[key]
53
- if result.nil? && !has_key?(key)
54
- self[key] = default.duplicable? ? default.dup : default
55
- else
56
- result
57
- end
58
- end
59
-
60
- def has_key?(key)
61
- @temp_settings.has_key?(key.to_sym)
62
- end
63
-
64
- #
65
- # Writes a setting's value
66
- #
67
- def []=(key, val)
68
- set_value_before_type_cast(key, val)
69
- @temp_settings[key.to_sym] = SettingAccessors::Internal.converter(value_type(key)).convert(val)
70
- end
71
-
72
- #
73
- # Tries to find a setting for this record.
74
- # If none is found, will return the default setting value
75
- # specified in the setting config file.
76
- #
77
- def get_or_default(key)
78
- fetch_and_store(key, SettingAccessors.setting_class.get_or_default(key, @record))
79
- end
80
-
81
- #
82
- # Tries to find a setting for this record first.
83
- # If none is found, tries to find a global setting with the same name
84
- #
85
- def get_or_global(key)
86
- fetch_and_store(key, SettingAccessors.setting_class.get(key))
87
- end
88
-
89
- #
90
- # Tries to find a setting for this record first,
91
- # if none is found, it will return the given value instead.
92
- #
93
- def get_or_value(key, value)
94
- fetch_and_store(key, value)
95
- end
96
-
97
- def get_with_fallback(key, fallback = nil)
98
- return self[key] if fallback.nil?
99
-
100
- case fallback.to_s
101
- when 'default' then get_or_default(key)
102
- when 'global' then get_or_global(key)
103
- else get_or_value(key, fallback)
104
- end
105
- end
106
-
107
- #
108
- # @return [String] the setting's value type in the +@record+'s context
109
- #
110
- def value_type(key)
111
- SettingAccessors::Internal.setting_value_type(key, @record)
112
- end
113
-
114
- #----------------------------------------------------------------
115
- # ActiveRecord Helper Methods Emulation
116
- #----------------------------------------------------------------
117
-
118
- def value_was(key, fallback = nil)
119
- return SettingAccessors.setting_class.get(key, @record) if fallback.nil?
120
-
121
- case fallback.to_s
122
- when 'default' then SettingAccessors.setting_class.get_or_default(key, @record)
123
- when 'global' then SettingAccessors.setting_class.get(key)
124
- else fallback
125
- end
126
- end
127
-
128
- def value_changed?(key)
129
- self[key] != value_was(key)
130
- end
131
-
132
- def value_before_type_cast(key)
133
- SettingAccessors::Internal.lookup_nested_hash(@values_before_type_casts, key.to_s) || self[key]
134
- end
135
-
136
- protected
137
-
138
- #
139
- # Keeps a record of the originally set value for a setting before it was
140
- # automatically converted.
141
- #
142
- def set_value_before_type_cast(key, value)
143
- @values_before_type_casts ||= {}
144
- @values_before_type_casts[key.to_s] = value
145
- end
146
-
147
- #
148
- # Validates the new setting values.
149
- # If there is an accessor for the setting, the errors will be
150
- # directly forwarded to it, otherwise to :base
151
- #
152
- # Please do not call this method directly, use the IntegrationValidator
153
- # class instead, e.g.
154
- #
155
- # validates_with SettingAccessors::IntegrationValidator
156
- #
157
- def validate!
158
- @temp_settings.each do |key, value|
159
- validation_errors = SettingAccessors.setting_class.validation_errors(key, value, @record)
160
- validation_errors.each do |message|
161
- if @record.respond_to?("#{key}=")
162
- @record.errors.add(key, message)
163
- else
164
- @record.errors.add :base, :invalid_setting, :name => key, :message => message
165
- end
166
- end
167
- end
168
- end
169
-
170
- #
171
- # Saves the new setting values into the database
172
- # Please note that there is no check if the values changed their
173
- # in the meantime.
174
- #
175
- # Also, this method expects that the settings were validated
176
- # before using #validate! and will therefore not perform
177
- # validations itself.
178
- #
179
- def persist!
180
- @temp_settings.each do |key, value|
181
- Setting.create_or_update(key, value, @record)
182
- end
183
- flush!
184
- end
185
-
186
- def flush!
187
- @temp_settings = {}
188
- end
189
- end
@@ -1,71 +0,0 @@
1
- #
2
- # This class hopefully will hopefully one day mimic ActiveRecord's
3
- # attribute assigning methods, meaning that a conversion to the column type
4
- # is done as soon as a new value is assigned by the programmer.
5
- #
6
- # If the value cannot be parsed in the required type, +nil+ is assigned.
7
- # Please make sure that you specify the correct validations in settings.yml
8
- # or assigned model to avoid this.
9
- #
10
- # Currently supported types:
11
- # - Fixnum
12
- # - String
13
- # - Boolean
14
- #
15
- # If the type is 'polymorphic', it is not converted at all.
16
- #
17
- class SettingAccessors::Converter
18
-
19
- def initialize(value_type)
20
- @value_type = value_type
21
- end
22
-
23
- #
24
- # Converts the setting's value to the correct type
25
- #
26
- def convert(new_value)
27
- #If the value is set to be polymorphic, we don't have to convert anything.
28
- return new_value if @value_type.to_s == 'polymorphic'
29
-
30
- #ActiveRecord only converts non-nil values to their database type
31
- #during assignment
32
- return new_value if new_value.nil?
33
-
34
- parse_method = :"parse_#{@value_type}"
35
-
36
- if private_methods.include?(parse_method)
37
- send(parse_method, new_value)
38
- else
39
- Rails.logger.warn("Invalid Setting type: #{@value_type}")
40
- new_value
41
- end
42
- end
43
-
44
- private
45
-
46
- def parse_boolean(value)
47
- case value
48
- when TrueClass, FalseClass
49
- value
50
- when String
51
- return true if %w[true 1].include?(value.downcase)
52
- return false if %w[false 0].include?(value.downcase)
53
- nil
54
- when Fixnum
55
- return true if value == 1
56
- return false if value.zero?
57
- nil
58
- else
59
- nil
60
- end
61
- end
62
-
63
- def parse_integer(value)
64
- value.to_i
65
- end
66
-
67
- def parse_string(value)
68
- value.to_s
69
- end
70
-
71
- end
@@ -1,15 +0,0 @@
1
- #
2
- # This class handles model validations for assigned records, e.g.
3
- # if the settings are accessed using the Accessor class in this module.
4
- # Only the new temp values are validated using the setting config.
5
- #
6
- # The main work is still done in the Accessor class, so we don't have
7
- # to access its instance variables here, this class acts as a wrapper
8
- # for Rails' validation chain
9
- #
10
-
11
- class SettingAccessors::IntegrationValidator < ActiveModel::Validator
12
- def validate(record)
13
- record.settings.send(:validate!)
14
- end
15
- end
@@ -1,144 +0,0 @@
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