sandyw-active_configuration 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # ActiveConfiguration
2
+
3
+ ActiveConfiguration is an engine that exposes a generic settings store to
4
+ ActiveRecord models. Made for very configurable applications, it allows you
5
+ to avoid implementing specific ways to store settings for each model that
6
+ needs such a configuration. If your application isn't very configurable,
7
+ ActiveConfiguration isn't what you want.
8
+
9
+ If you had a `Category` model that only had a configurable `sort` attribute,
10
+ ActiveConfiguration would be overkill. Rather, you would just read and write
11
+ values using a specific `sort` column and restrict the allowed values using
12
+ something like `validates_inclusion_of`.
13
+
14
+ However, if your `Category` model was more flexible in its configuration, you
15
+ may want a `sort` setting, a `limit` setting and multiple `price_filter`
16
+ settings that can be configured by your end user. Without ActiveConfiguration,
17
+ you would have to develop a way to store and validate these settings for this
18
+ specific scenario. The `sort` and `limit` settings are simple but because
19
+ `price_filter` can accept multiple rules, you'd have to set up an additional
20
+ model. Still, this isn't really an issue when you're dealing with just a single
21
+ configurable model. When you're dealing with many, things tend to get messy.
22
+
23
+ With ActiveConfiguration, all of your settings, even for `price_filter`, can
24
+ be stored in a generic way. ActiveConfiguration provides a place to store
25
+ settings for each of your models and even handles validation when you restrict
26
+ the allowed values or format of an option.
27
+
28
+ ## Source
29
+
30
+ The source for this engine is located at:
31
+
32
+ http://github.com/tsmango/active_configuration
33
+
34
+ ## Installation
35
+
36
+ Add the following to your Gemfile:
37
+
38
+ gem 'active_configuration'
39
+
40
+ Generate the migration for the `settings` table:
41
+
42
+ rails g active_configuration:install
43
+
44
+ Note: The table can be changed from `settings` to something else by specifying
45
+ a config option in an initializer like:
46
+
47
+ # config/initializers/active_configuration.rb
48
+
49
+ Rails.configuration.active_configuration_table_name = 'active_configuration_settings'
50
+
51
+ Migrate your database:
52
+
53
+ rake db:migrate
54
+
55
+ ## Example Configuration
56
+
57
+ class Category < ActiveRecord::Base
58
+ configure do
59
+ option :sort do
60
+ default 'alphabetical'
61
+ restrict 'alphabetical', 'manual'
62
+ end
63
+
64
+ option :limit do
65
+ format 'fixnum'
66
+ end
67
+
68
+ option :price_filter do
69
+ format 'float'
70
+ modifiers 'eq', 'lt', 'gt', 'lte', 'gte'
71
+ multiple true
72
+ end
73
+ end
74
+ end
75
+
76
+ After installing ActiveConfiguration, the #configure block is available to
77
+ every ActiveRecord model. If the #configure block is defined with a valid
78
+ configuration, additional methods are made available on the model.
79
+
80
+ ## Example Usage
81
+
82
+ Given we have defined the `Category` class above, instances will now have a #settings
83
+ method where settings can be read from and written to.
84
+
85
+ >> category = Category.create(:name => 'Vinyl Records')
86
+ => #<Category id: 1, name: "Vinyl Records", created_at: "2011-08-03 15:46:11", updated_at: "2011-08-03 15:46:11">
87
+
88
+ ?> category.settings
89
+ => #<ActiveConfiguration::SettingManager:0x10e7d1950 @configurable=#<Category id: 1, name: "Vinyl Records", created_at: "2011-08-03 15:46:11", updated_at: "2011-08-03 15:46:11">>
90
+
91
+ ?> category.settings[:sort]
92
+ => {:value=>"alphabetical", :modifier=>nil}
93
+
94
+ ?> category.settings[:sort][:value]
95
+ => "alphabetical"
96
+
97
+ ?> category.settings[:sort][:value] = 'manual'
98
+ => "manual"
99
+
100
+ ?> category.settings[:price_filter]
101
+ => []
102
+
103
+ ?> category.settings[:price_filter] = [{:modifier => 'gt', :value => 10.00}, {:modifier => 'lte', :value => 25.00}]
104
+ => [{:value=>10.0, :modifier=>"gt"}, {:value=>25.0, :modifier=>"lte"}]
105
+
106
+ ?> category.save
107
+ => true
108
+
109
+ ?> category.settings[:sort][:value]
110
+ => "manual"
111
+
112
+ ?> category.settings[:price_filter]
113
+ => [{:value=>10.0, :modifier=>"gt"}, {:value=>25.0, :modifier=>"lte"}]
114
+
115
+ Note:
116
+
117
+ Settings are only committed after a `save` of the configurable model. If any
118
+ validation errors should arise, the `save` on the model will return false and
119
+ errors will be added to the model's errors collection.
120
+
121
+ ## Testing Environment
122
+
123
+ The spec/ directory contains a skeleton Rails 3.0.0 application for testing
124
+ purposes. All specs can be found in spec/spec/.
125
+
126
+ To run the specs, do the following from the root of active\_configuration:
127
+
128
+ bundle install --path=vendor/bundles --binstubs
129
+ bin/rspec spec
130
+
131
+ ## License
132
+
133
+ Copyright &copy; 2011 Thomas Mango, released under the MIT license.
@@ -0,0 +1,39 @@
1
+ module ActiveConfiguration
2
+
3
+ # Holds the details of a Setting and is attached to any model configured
4
+ # for use with ActiveConfiguration.
5
+ #
6
+ # Note: Settings are not meant to be created and updated directly.
7
+ # Rather, they should be managed through the #settings method available
8
+ # through the configured model. See ActiveRecord::Configuration.
9
+ class Setting < ActiveRecord::Base
10
+
11
+ # To avoid collisions with another Setting model that isn't from
12
+ # ActiveConfiguration, this model and table is namespaced.
13
+ self.table_name = ActiveConfiguration::Config.table_name
14
+
15
+ # The model this Setting was created against.
16
+ belongs_to :configurable, :polymorphic => true
17
+
18
+ # Settings are looked up from their key.
19
+ scope :with_key, lambda { |key|
20
+ where(:key => key.to_s)
21
+ }
22
+
23
+ # Settings should be created through a configured model's
24
+ # #active_configuration_settings relationship.
25
+ attr_protected :configurable_type
26
+ attr_protected :configurable_id
27
+
28
+ # Settings must be related to some other model, have a key
29
+ # and have a value. They do not necessarily need a modifier.
30
+ validates_presence_of :configurable_type
31
+ validates_presence_of :configurable_id
32
+ validates_presence_of :key
33
+ validate :presence_of_value
34
+
35
+ def presence_of_value
36
+ errors.add :value, "can't be blank" if value.nil?
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ require 'active_configuration/engine'
2
+ require 'active_configuration/table_name'
3
+ require 'active_record/configuration'
4
+
5
+ ActiveRecord::Base.class_eval do
6
+ include ActiveRecord::Configuration
7
+ end
@@ -0,0 +1,38 @@
1
+ require 'active_configuration/option'
2
+
3
+ module ActiveConfiguration
4
+
5
+ # Holds a set of configuration details. An instance of this object is created
6
+ # when the #configure block is used on an ActiveRecord model. For more details
7
+ # see ActiveRecord::Configuration#configure.
8
+ class Base
9
+ attr_accessor :options
10
+
11
+ # Initializes an empty options hash to store ActiveConfiguration::Option
12
+ # instances containing configuration details.
13
+ def initialize
14
+ @options = HashWithIndifferentAccess.new
15
+ end
16
+
17
+ # Creates and accepts the configuration details for an Option.
18
+ #
19
+ # An example of setting an option with a block:
20
+ #
21
+ # option :sort do
22
+ # default 'alphabetical'
23
+ # restrict 'alphabetical', 'manual'
24
+ # end
25
+ #
26
+ # Here, the #option method is called and passed the key of :sort and then the
27
+ # the block that follows. The block given to #option is then evaluated against
28
+ # a new instance of ActiveConfiguration::Option.
29
+ #
30
+ # @param [Symbol] key the key for this option and settings against this option.
31
+ # @param [Proc] block what should be evaluated against the option.
32
+ def option(key, &block)
33
+ opt = Option.new(key)
34
+ opt.instance_eval(&block)
35
+ @options[key.to_sym] = opt
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ module ActiveConfiguration
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveConfiguration
2
+ class Error < RuntimeError; end
3
+ end
@@ -0,0 +1,129 @@
1
+ require 'active_configuration/error'
2
+
3
+ module ActiveConfiguration
4
+
5
+ # Holds the configuration details of a single option. An instance of this
6
+ # object is created when the #option block is used within a #configure block.
7
+ class Option
8
+
9
+ # ActiveSupport::Callbacks are included so that the #validate! method can
10
+ # be automaticalled called after each modification to this option.
11
+ include ActiveSupport::Callbacks
12
+
13
+ # There is a callback called :validate that should be watched.
14
+ define_callbacks :validate
15
+
16
+ # After the :validate callback, execute the #validate! method.
17
+ set_callback :validate, :after, :validate!
18
+
19
+ attr_accessor :key, :default_value, :allowed_format, :allowed_values, :allowed_modifiers, :allow_multiple
20
+
21
+ alias :allow_multiple? :allow_multiple
22
+
23
+ # Initializes the default values for all deatils of this options. This
24
+ # includes no default value, no restricted values set, no modifiers,
25
+ # and a 'string' format.
26
+ #
27
+ # @param [Symbol] key the key for this option and settings against this option.
28
+ def initialize(key)
29
+ @key = key
30
+ @default_value = nil
31
+ @allowed_format = 'string'
32
+ @allowed_values = nil
33
+ @allowed_modifiers = nil
34
+ @allow_multiple = false
35
+ end
36
+
37
+ # Sets the default value for this option. This cannot be used in
38
+ # conjunction with the multiple options. Additionally, if a set
39
+ # of allowed values is set with the #restrict method, this default
40
+ # value must appear in that list of allowed values.
41
+ #
42
+ # @param value the value to be used as the default for this option.
43
+ def default(value)
44
+ run_callbacks :validate do
45
+ @default_value = (value.is_a?(Symbol) ? value.to_s : value)
46
+ end
47
+ end
48
+
49
+ # Sets a specific format that the value of this option must conform
50
+ # to. Allowed formats include: 'string', 'fixnum', 'float', 'boolean',
51
+ # 'email', 'url' or a /regular expression/.
52
+ #
53
+ # @param [String, Regexp] value the format this option must be given in.
54
+ def format(value)
55
+ run_callbacks :validate do
56
+ @allowed_format = (value.is_a?(Symbol) ? value.to_s : value)
57
+ end
58
+ end
59
+
60
+ # Restricts the allowed values of this option to a given list of values.
61
+ #
62
+ # Example:
63
+ #
64
+ # restrict 'alphabetical', 'manual'
65
+ #
66
+ # @param [Array] values the allowsed values for this option.
67
+ def restrict(*values)
68
+ run_callbacks :validate do
69
+ @allowed_values = values.collect{|value| (value.is_a?(Symbol) ? value.to_s : value)}
70
+ end
71
+ end
72
+
73
+ # Restricts the allows modifiers of this option to a given list of modifers.
74
+ #
75
+ # Example:
76
+ #
77
+ # modifiers 'eq', 'lt', 'gt', 'lte', 'gte'
78
+ #
79
+ # @param [Array] values the allowed modifiers for this option
80
+ def modifiers(*values)
81
+ run_callbacks :validate do
82
+ @allowed_modifiers = values.collect{|value| (value.is_a?(Symbol) ? value.to_s : value)}
83
+ end
84
+ end
85
+
86
+ # Whether or not this option can have multiple settings set against it.
87
+ #
88
+ # @param [TrueClass, FalseClass] value either true or false for whether
89
+ # this option should allow multiple settings or not.
90
+ def multiple(value)
91
+ run_callbacks :validate do
92
+ @allow_multiple = value
93
+ end
94
+ end
95
+
96
+ # Validates how the specified configuration options are used with one
97
+ # another. If an invalid configuration is detected, such as using both
98
+ # the #default method and setting #multiple to true, an exception of
99
+ # ActiveConfiguration::Error is raised.
100
+ #
101
+ # Note: This method is automatically called after each of the
102
+ # configuration methods are run.
103
+ def validate!
104
+
105
+ # If both a default value and a list of allowed values are given,
106
+ # the default value must appear in the list of allowed values.
107
+ if !@default_value.nil? and !@allowed_values.nil? and !@allowed_values.include?(@default_value)
108
+ raise ActiveConfiguration::Error, "The default value '#{@default_value}' isn't present in the list of allowed values."
109
+ end
110
+
111
+ # If multiple is set, it must be set to either true or false.
112
+ if ![TrueClass, FalseClass].include?(@allow_multiple.class)
113
+ raise ActiveConfiguration::Error, 'The multiple option requires a boolean.'
114
+ end
115
+
116
+ # If a default value is given, multiple must be false.
117
+ if !@default_value.nil? and @allow_multiple
118
+ raise ActiveConfiguration::Error, 'The default value cannot be set in combination with the multiple option.'
119
+ end
120
+
121
+ # If a format is specified, it must be an allowed format. This
122
+ # includes 'string', 'fixnum', 'float', 'boolean', 'email', 'url'
123
+ # or a /regular exprssion.
124
+ if !@allowed_format.nil? and !['string', 'fixnum', 'float', 'boolean', 'email', 'url'].include?(@allowed_format) and !@allowed_format.is_a?(Regexp)
125
+ raise ActiveConfiguration::Error, "The format #{@allowed_format} is not supported."
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,94 @@
1
+ require 'active_configuration/setting_proxy'
2
+
3
+ module ActiveConfiguration
4
+
5
+ # Returns a SettingProxy object for any option that has been configured.
6
+ class SettingManager
7
+ attr_accessor :configurable
8
+ attr_accessor :settings
9
+
10
+ # Initializes this SettingManager and keeps track of what model this
11
+ # SettingManager is proxying settings for.
12
+ #
13
+ # @param [ActiveRecord::Base] configurable the model that hsa been
14
+ # configured for use with ActiveConfiguration.
15
+ def initialize(configurable)
16
+ @configurable = configurable
17
+ @settings = Hash.new
18
+ end
19
+
20
+ # Provides access to setting details for a setting at the given key.
21
+ #
22
+ # @param [Symbol] key the key of the requested setting.
23
+ #
24
+ # @return [Hash, Array, NilClass] the Hash or Array of Hashes for the
25
+ # setting with the given key or nil if there isn't a match.
26
+ def [](key)
27
+ if @configurable.class.configuration.options.has_key?(key)
28
+ @settings[key] ||= SettingProxy.new(self, key)
29
+
30
+ return @settings[key].value
31
+ end
32
+
33
+ return nil
34
+ end
35
+
36
+ # Replaces the Hash or Array of Hashes for the setting with the given
37
+ # key with the given value.
38
+ #
39
+ # @param [Symbol] key the key of the requested setting.
40
+ #
41
+ # @return [Hash, Array, NilClass] the Hash or Array of Hashes for the
42
+ # setting with the given key or nil if there isn't a match.
43
+ def []=(key, value)
44
+ if @configurable.class.configuration.options.has_key?(key)
45
+ @settings[key] ||= SettingProxy.new(self, key)
46
+ @settings[key].replace(value)
47
+
48
+ return @settings[key].value
49
+ end
50
+
51
+ return nil
52
+ end
53
+
54
+ # Writes over multiple settings at once.
55
+ #
56
+ # @param [Hash] replacement_settings the has of settings to be set.
57
+ def write_settings(replacement_settings = {})
58
+ replacement_settings.each_pair do |key, value|
59
+ self[key] = value
60
+ end
61
+ end
62
+
63
+ # Runs validations against all settings with pending modificaitons.
64
+ # Any errors are added to @configurable.errors[:settings].
65
+ def validate
66
+ settings.values.collect{|setting| setting.validate}
67
+ end
68
+
69
+ # Saves all settings with pending modificaitons.
70
+ #
71
+ # @return [Boolean] whether or not the save was successful.
72
+ def save
73
+ return !settings.values.collect{|setting| setting.save}.include?(false)
74
+ end
75
+
76
+ # Writes over multiple settings and saves all setting updates at once.
77
+ #
78
+ # @param [Hash] replacement_settings the has of settings to be set.
79
+ #
80
+ # @return [Boolean] whether or not the save was successful.
81
+ def update_settings(replacement_settings = {})
82
+ write_settings(replacement_settings)
83
+
84
+ validate
85
+
86
+ return (@configurable.errors[:settings].empty? ? save : false)
87
+ end
88
+
89
+ # Resets any pending setting modifications.
90
+ def reload
91
+ @settings = Hash.new
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,210 @@
1
+ require 'active_configuration/error'
2
+
3
+ module ActiveConfiguration
4
+
5
+ # Handles the reading and writing of ActiveConfiguration::Setting objects
6
+ # and ensures configuration requirements are upheld.
7
+ class SettingProxy
8
+ attr_accessor :manager
9
+ attr_accessor :key
10
+ attr_accessor :value
11
+
12
+ # Initializes a new ActiveConfiguration::SettingProxy with a related
13
+ # SettingManager and a key for this setting. This setting's modifiers
14
+ # and values are cached locally as either a Hash or an Array for later
15
+ # access and manipulation.
16
+ #
17
+ # @param [ActiveConfiguration::SettingManager] manager the manager which
18
+ # holds this SettingProxy and has access to the configurable object that
19
+ # this setting will be attached to.
20
+ # @param [Symbol] key the key for this setting and its related option.
21
+ def initialize(manager, key)
22
+ @manager, @key = manager, key
23
+
24
+ if settings = @manager.configurable.active_configuration_settings.with_key(@key).all
25
+ if option.allow_multiple?
26
+ @value = settings.collect{|setting| {:value => coerce(setting.value), :modifier => setting.modifier}}
27
+
28
+ else
29
+ setting = settings.first
30
+
31
+ @value = {
32
+ :modifier => (setting ? setting.modifier : nil),
33
+ :value => coerce(setting ? setting.value : option.default_value)
34
+ }
35
+ end
36
+ end
37
+ end
38
+
39
+ # Replaces the underlying Hash or Array with a replacement. This handles
40
+ # reverting to defaults when nil is given as the value.
41
+ #
42
+ # Note: Athough Hashes given may contain keys other than :modifier and
43
+ # :value, all other keys will be stripped out and not saved.
44
+ #
45
+ # @raise [ArgumentError] if a Hash, Array or NilClass isn't given for a
46
+ # multiple option.
47
+ # @raise [ArgumentError] if a Hash or NilClass isn't given for a non-multiple
48
+ # option.
49
+ #
50
+ # @param [Hash] value_with_modifier the Hash or Array of Hashes containing
51
+ # modifier and value pairs.
52
+ #
53
+ # @return [Hash, Array] the requested change.
54
+ def replace(value_with_modifier)
55
+ if option.allow_multiple?
56
+ if value_with_modifier.is_a?(Hash) or value_with_modifier.is_a?(Array)
57
+ value_with_modifier = [value_with_modifier].flatten.collect do |value_with_modifier|
58
+ {:modifier => nil, :value => nil}.merge( value_with_modifier.slice(*[:modifier, :value]) )
59
+ end
60
+ value_with_modifier.delete({:modifier => nil, :value => nil})
61
+ elsif value_with_modifier.nil?
62
+ value_with_modifier = []
63
+ else
64
+ raise ArgumentError, "Array expected."
65
+ end
66
+ else
67
+ if value_with_modifier.is_a?(Hash)
68
+ value = value_with_modifier.with_indifferent_access[:value]
69
+ modifier = value_with_modifier.with_indifferent_access[:modifier]
70
+ value_with_modifier = {:modifier => nil, :value => nil}.merge( { value: value, modifier: modifier } )
71
+ elsif value_with_modifier.nil?
72
+ value_with_modifier = { value: coerce(option.default_value) }
73
+ else
74
+ raise ArgumentError, "Hash expected."
75
+ end
76
+ end
77
+
78
+ return (@value = value_with_modifier)
79
+ end
80
+
81
+ # Checks modifiers and values on this setting for validation errors and, if
82
+ # found, adds those errors to this proxy's model's collection of errors.
83
+ def validate
84
+ errors = Array.new
85
+
86
+ [value].flatten.each do |value_with_modifier|
87
+ value = value_with_modifier[:value]
88
+ modifier = value_with_modifier[:modifier]
89
+
90
+ if !option.allowed_values.nil? and !option.allowed_values.include?(value)
91
+ errors << "The value '#{value}' for the '#{option.key}' setting isn't present in the list of allowed values."
92
+ end
93
+
94
+ if !option.allowed_format.nil?
95
+ case option.allowed_format
96
+ when 'string'
97
+ if !value.is_a?(String)
98
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a String."
99
+ end
100
+ when 'fixnum'
101
+ if !value.is_a?(Fixnum)
102
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a Fixnum."
103
+ end
104
+ when 'float'
105
+ if !value.is_a?(Float) and !value.is_a?(Fixnum)
106
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a Float."
107
+ end
108
+ when 'boolean'
109
+ if !coerce(value).is_a?(TrueClass) and !coerce(value).is_a?(FalseClass)
110
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a Boolean."
111
+ end
112
+ when 'email'
113
+ if !value[/^[A-Z0-9_\.%\+\-\']+@(?:[A-Z0-9\-]+\.)+(?:[A-Z]{2,4}|museum|travel)$/i]
114
+ errors << "The value '#{value}' for the '#{option.key}' setting is not an Email Address."
115
+ end
116
+ when 'url'
117
+ if !value[URI.regexp]
118
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a URL."
119
+ end
120
+ end
121
+
122
+ if option.allowed_format.is_a?(Regexp) and !value[option.allowed_format]
123
+ errors << "The value '#{value}' for the '#{option.key}' setting is not in the correct format."
124
+ end
125
+ end
126
+
127
+ if !modifier.nil? and !option.allowed_modifiers.nil? and !option.allowed_modifiers.include?(modifier)
128
+ errors << "The modifier '#{modifier}' for the '#{option.key}' setting isn't present in the list of allowed modifiers."
129
+ end
130
+ end
131
+
132
+ errors.each do |error|
133
+ @manager.configurable.errors.add(:settings, error)
134
+ end
135
+ end
136
+
137
+ # Saves this setting's modifiers and values.
138
+ #
139
+ # @return [Boolean] whether or not the save was successful.
140
+ def save
141
+ save_status = true
142
+ original_setting_ids = @manager.configurable.active_configuration_settings.with_key(@key).collect(&:id)
143
+ replaced_setting_ids = []
144
+
145
+ [value].flatten.each do |value_with_modifier|
146
+ if (setting = @manager.configurable.active_configuration_settings.create(:key => @key, :modifier => value_with_modifier[:modifier], :value => value_with_modifier[:value])).new_record?
147
+ save_status = false && break
148
+ else
149
+ replaced_setting_ids << setting.id
150
+ end
151
+ end
152
+
153
+ @manager.configurable.active_configuration_settings.reload
154
+ @manager.configurable.active_configuration_settings.with_key(@key).where(:id => (save_status ? original_setting_ids : replaced_setting_ids)).destroy_all
155
+
156
+ @manager.settings.delete(@key)
157
+
158
+ return save_status
159
+ end
160
+
161
+ # Returns the Hash or Array representation of the underlying stored settings
162
+ # depending on whether or not this is a multiple option.
163
+ #
164
+ # @return [Hash] the value and modifier, as a Hash, for a non-multiple
165
+ # option.
166
+ # @return [Array] the array of hashes containing value and modifiers, like
167
+ # those returned on a non-multiple options, for a multiple option.
168
+ def inspect
169
+ return @value.inspect
170
+ end
171
+
172
+ private
173
+
174
+ # Returns this SettingProxy's related Option based on the given key. This
175
+ # is necessary to ensure all configuration requirements are upheld during
176
+ # reads and writes.
177
+ #
178
+ # @return [ActiveConfiguration::Option] the option for this setting.
179
+ def option
180
+ return @manager.configurable.class.configuration.options[key]
181
+ end
182
+
183
+ # Coerces a stored String value into the expected type if an allowed format
184
+ # is explicitly set on this setting's option.
185
+ #
186
+ # Note: Because values are validated against given formats when updated,
187
+ # it is assumed that they can be properly coerced back to their intended
188
+ # type.
189
+ #
190
+ # @param [String] value the value stored in the database that must be coerced.
191
+ #
192
+ # @return [String, Fixnum, Float, TrueClass, FalseClass] the properly coerced
193
+ # value, assuming the value should be coerced.
194
+ def coerce(value)
195
+ if !value.nil? and !option.allowed_format.nil?
196
+ case option.allowed_format
197
+ when 'fixnum'
198
+ value = value.to_i
199
+ when 'float'
200
+ value = value.to_f
201
+ when 'boolean'
202
+ value = true if [ 'true', 't', '1' ].include?(value)
203
+ value = false if [ 'false', 'f', '0' ].include?(value)
204
+ end
205
+ end
206
+
207
+ return value
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,20 @@
1
+ module ActiveConfiguration
2
+
3
+ # Holds the configuration details of this ActiveConfiguration install.
4
+ class Config
5
+
6
+ # Returns the name of the table holding ActiveConfiguration::Settings. This
7
+ # table defaults to `settings` but can be changed with an initializer like:
8
+ #
9
+ # Rails.configuration.active_configuration_table_name = 'active_configuration_settings'
10
+ #
11
+ # @return [String] the table name holding ActiveConfiguration::Settings.
12
+ def self.table_name
13
+ if Rails.configuration.respond_to?(:active_configuration_table_name)
14
+ return Rails.configuration.active_configuration_table_name
15
+ end
16
+
17
+ (Rails.configuration.active_configuration_table_name = 'settings')
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveConfiguration
2
+ class Version
3
+ MAJOR = 1
4
+ MINOR = 1
5
+ PATCH = 0
6
+ BUILD = nil
7
+
8
+ STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
9
+ end
10
+ end
@@ -0,0 +1,145 @@
1
+ require 'active_configuration/base'
2
+ require 'active_configuration/setting_manager'
3
+ require 'active_configuration/error'
4
+
5
+ module ActiveRecord
6
+
7
+ # Exposes a #configure method to all ActiveRecord classes and if configured,
8
+ # defines a #settings method for reading and writing settings.
9
+ module Configuration
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ # Configures the current ActiveRecord class to allow specific options.
17
+ # After being configured, a #settings method will be defined against
18
+ # all instances as well as a has_many :active_configuration_settings
19
+ # relationship for storing settings.
20
+ #
21
+ # Example configuration:
22
+ #
23
+ # class Category < ActiveRecord::Base
24
+ # configure do
25
+ # option :sort do
26
+ # default 'alphabetical'
27
+ # restrict 'alphabetical', 'manual'
28
+ # end
29
+ #
30
+ # option :limit do
31
+ # format 'fixnum'
32
+ # end
33
+ #
34
+ # option :price_filter do
35
+ # format 'float'
36
+ # modifiers 'eq', 'lt', 'gt', 'lte', 'gte'
37
+ # multiple true
38
+ # end
39
+ # end
40
+ # end
41
+ #
42
+ # The #configure block can only contain #option blocks. Within each
43
+ # option block may be a number of methods such as:
44
+ #
45
+ # * default
46
+ # A default value. Cannot be used in conjunction with multiple.
47
+ #
48
+ # * format -
49
+ # A specific format, including: 'string', 'fixnum', 'float',
50
+ # 'boolean', 'email', 'url' or a /regular expression/. Defaults
51
+ # to 'string'.
52
+ #
53
+ # * restrict -
54
+ # An array of allowed values.
55
+ #
56
+ # * modifiers -
57
+ # An array of allowed modifiers.
58
+ #
59
+ # * multiple -
60
+ # Whether or not multiple Settings can be set against the single
61
+ # option. Must be set to either true or false. Defaults to false.
62
+ #
63
+ #
64
+ # @param [Proc] block the configuration block that contains option blocks.
65
+ def configure(&block)
66
+ class_eval <<-EOV
67
+
68
+ # Includes the #settings method for reading and writing settings
69
+ # against any instances of this class.
70
+ include ActiveRecord::Configuration::InstanceMethods
71
+
72
+ # Where the actual settings are stored against the instance.
73
+ has_many :active_configuration_settings, :as => :configurable, :class_name => 'ActiveConfiguration::Setting'
74
+
75
+ # Validates are run on settings along with other validations.
76
+ validate :validate_settings
77
+
78
+ # After being saved, outstanding setting modifications are saved.
79
+ after_save :save_settings
80
+
81
+ # Returns the configuration details of this class.
82
+ def self.configuration
83
+ @configuration ||= ActiveConfiguration::Base.new
84
+ end
85
+ EOV
86
+
87
+ # Evaluates the configuration block given to #configure. Each
88
+ # option block is then evaluated and options are setup. For more
89
+ # details, see ActiveConfiguration::Base.
90
+ configuration.instance_eval(&block)
91
+ end
92
+ end
93
+
94
+ module InstanceMethods
95
+
96
+ # Returns an ActiveConfiguration::SettingManager that proxies
97
+ # all reads and writes of settings to ActiveConfiguration::SettingProxy
98
+ # objects for the specific setting requested.
99
+ def settings
100
+ @setting_manager ||= ActiveConfiguration::SettingManager.new(self)
101
+ end
102
+
103
+ # Writes over multiple settings at once.
104
+ #
105
+ # @param [Hash] replacement_settings the has of settings to be set.
106
+ def settings=(replacement_settings = {})
107
+ settings.write_settings(replacement_settings)
108
+ end
109
+
110
+ # Runs validations against all settings with pending modificaitons.
111
+ # Any errors are added to #errors[:settings].
112
+ def validate_settings
113
+ settings.validate
114
+ end
115
+
116
+ # Saves all settings with pending modificaitons.
117
+ #
118
+ # @return [Boolean] whether or not the save was successful.
119
+ def save_settings
120
+ settings.save
121
+ end
122
+
123
+ # Writes over multiple settings and saves all setting updates at once.
124
+ #
125
+ # @param [Hash] replacement_settings the has of settings to be set.
126
+ #
127
+ # @return [Boolean] whether or not the save was successful.
128
+ def update_settings(replacement_settings = {})
129
+ settings.update_settings(replacement_settings)
130
+ end
131
+
132
+ # Overrides this model's #reload method by first resetting any requested
133
+ # changes to settings and then continuing to perform a standard #reload.
134
+ #
135
+ # Note: Can this be accomplished with a callback after #reload rather
136
+ # than overriding the #reload method?
137
+ #
138
+ # @param options any options that must be passed along to this methods
139
+ # original #reload method.
140
+ def reload(options = nil)
141
+ settings.reload && super(options)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,27 @@
1
+ module ActiveConfiguration
2
+
3
+ # Generates a migration for the settings table.
4
+ #
5
+ # Example:
6
+ #
7
+ # rails g active_configuration:install
8
+ class InstallGenerator < Rails::Generators::Base
9
+ include Rails::Generators::Migration
10
+
11
+ def self.source_root
12
+ File.join(File.dirname(__FILE__), 'templates')
13
+ end
14
+
15
+ def self.next_migration_number(dirname)
16
+ if ActiveRecord::Base.timestamped_migrations
17
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
18
+ else
19
+ "%.3d" % (current_migration_number(dirname) + 1)
20
+ end
21
+ end
22
+
23
+ def create_migration_file
24
+ migration_template('create_active_configuration_settings.rb', 'db/migrate/create_active_configuration_settings.rb')
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ class CreateActiveConfigurationSettings < ActiveRecord::Migration
2
+ def self.up
3
+ create_table ActiveConfiguration::Config.table_name do |t|
4
+ t.string :configurable_type
5
+ t.integer :configurable_id
6
+ t.string :key
7
+ t.string :modifier
8
+ t.text :value
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table ActiveConfiguration::Config.table_name
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sandyw-active_configuration
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thomas Mango
9
+ - Sandy Weber
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-06-10 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: 3.0.0
31
+ - !ruby/object:Gem::Dependency
32
+ name: sqlite3-ruby
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: sandyw-active_configuration
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: rspec-rails
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: yard
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: jeweler
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: activerecord
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: 3.0.0
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: 3.0.0
127
+ - !ruby/object:Gem::Dependency
128
+ name: activesupport
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: 3.0.0
135
+ type: :runtime
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: 3.0.0
143
+ description: ! "ActiveConfiguration is an engine that exposes a generic settings store
144
+ to \n ActiveRecord models. Made for very configurable applications,
145
+ it allows you \n to avoid implementing specific ways to store
146
+ settings for each model that \n needs such configuration. If
147
+ your application isn't very configurable, \n ActiveConfiguration
148
+ is probably overkill."
149
+ email: sandy.j.weber@gmail.com
150
+ executables: []
151
+ extensions: []
152
+ extra_rdoc_files:
153
+ - README.md
154
+ files:
155
+ - app/models/active_configuration/setting.rb
156
+ - lib/active_configuration.rb
157
+ - lib/active_configuration/base.rb
158
+ - lib/active_configuration/engine.rb
159
+ - lib/active_configuration/error.rb
160
+ - lib/active_configuration/option.rb
161
+ - lib/active_configuration/setting_manager.rb
162
+ - lib/active_configuration/setting_proxy.rb
163
+ - lib/active_configuration/table_name.rb
164
+ - lib/active_configuration/version.rb
165
+ - lib/active_record/configuration.rb
166
+ - lib/generators/active_configuration/install/install_generator.rb
167
+ - lib/generators/active_configuration/install/templates/create_active_configuration_settings.rb
168
+ - README.md
169
+ homepage: http://github.com/sandyw/active_configuration
170
+ licenses: []
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ none: false
177
+ requirements:
178
+ - - ! '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ none: false
183
+ requirements:
184
+ - - ! '>='
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubyforge_project:
189
+ rubygems_version: 1.8.24
190
+ signing_key:
191
+ specification_version: 3
192
+ summary: A generic settings store for Rails 3.x ActiveRecord models.
193
+ test_files: []