active_configuration 1.0.0

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.
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,35 @@
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
+ set_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
+ validates_presence_of :value
34
+ end
35
+ 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,202 @@
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) or value_with_modifier.is_a?(NilClass)
57
+ value_with_modifier = [value_with_modifier].flatten.collect{|value_with_modifier| {:modifier => nil, :value => nil}.merge(value_with_modifier.nil? ? {} : value_with_modifier.slice(*[:modifier, :value]))}
58
+ value_with_modifier.delete({:modifier => nil, :value => nil})
59
+ else
60
+ raise ArgumentError, "Array expected."
61
+ end
62
+ else
63
+ if value_with_modifier.is_a?(Hash) or value_with_modifier.is_a?(NilClass)
64
+ value_with_modifier = {:modifier => nil, :value => nil}.merge(value_with_modifier.nil? ? {:value => coerce(option.default_value)} : value_with_modifier.slice(*[:modifier, :value]))
65
+ else
66
+ raise ArgumentError, "Hash expected."
67
+ end
68
+ end
69
+
70
+ return (@value = value_with_modifier)
71
+ end
72
+
73
+ # Checks modifiers and values on this setting for validation errors and, if
74
+ # found, adds those errors to this proxy's model's collection of errors.
75
+ def validate
76
+ errors = Array.new
77
+
78
+ [value].flatten.each do |value_with_modifier|
79
+ value = value_with_modifier[:value]
80
+ modifier = value_with_modifier[:modifier]
81
+
82
+ if !option.allowed_values.nil? and !option.allowed_values.include?(value)
83
+ errors << "The value '#{value}' for the '#{option.key}' setting isn't present in the list of allowed values."
84
+ end
85
+
86
+ if !option.allowed_format.nil?
87
+ case option.allowed_format
88
+ when 'string'
89
+ if !value.is_a?(String)
90
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a String."
91
+ end
92
+ when 'fixnum'
93
+ if !value.is_a?(Fixnum)
94
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a Fixnum."
95
+ end
96
+ when 'float'
97
+ if !value.is_a?(Float) and !value.is_a?(Fixnum)
98
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a Float."
99
+ end
100
+ when 'boolean'
101
+ if !value.is_a?(TrueClass) and !value.is_a?(FalseClass)
102
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a Boolean."
103
+ end
104
+ when 'email'
105
+ if !value[/^[A-Z0-9_\.%\+\-\']+@(?:[A-Z0-9\-]+\.)+(?:[A-Z]{2,4}|museum|travel)$/i]
106
+ errors << "The value '#{value}' for the '#{option.key}' setting is not an Email Address."
107
+ end
108
+ when 'url'
109
+ if !value[URI.regexp]
110
+ errors << "The value '#{value}' for the '#{option.key}' setting is not a URL."
111
+ end
112
+ end
113
+
114
+ if option.allowed_format.is_a?(Regexp) and !value[option.allowed_format]
115
+ errors << "The value '#{value}' for the '#{option.key}' setting is not in the correct format."
116
+ end
117
+ end
118
+
119
+ if !modifier.nil? and !option.allowed_modifiers.nil? and !option.allowed_modifiers.include?(modifier)
120
+ errors << "The modifier '#{modifier}' for the '#{option.key}' setting isn't present in the list of allowed modifiers."
121
+ end
122
+ end
123
+
124
+ errors.each do |error|
125
+ @manager.configurable.errors.add(:settings, error)
126
+ end
127
+ end
128
+
129
+ # Saves this setting's modifiers and values.
130
+ #
131
+ # @return [Boolean] whether or not the save was successful.
132
+ def save
133
+ save_status = true
134
+ original_setting_ids = @manager.configurable.active_configuration_settings.with_key(@key).collect(&:id)
135
+ replaced_setting_ids = []
136
+
137
+ [value].flatten.each do |value_with_modifier|
138
+ if (setting = @manager.configurable.active_configuration_settings.create(:key => @key, :modifier => value_with_modifier[:modifier], :value => value_with_modifier[:value])).new_record?
139
+ save_status = false && break
140
+ else
141
+ replaced_setting_ids << setting.id
142
+ end
143
+ end
144
+
145
+ @manager.configurable.active_configuration_settings.reload
146
+ @manager.configurable.active_configuration_settings.with_key(@key).where(:id => (save_status ? original_setting_ids : replaced_setting_ids)).destroy_all
147
+
148
+ @manager.settings.delete(@key)
149
+
150
+ return save_status
151
+ end
152
+
153
+ # Returns the Hash or Array representation of the underlying stored settings
154
+ # depending on whether or not this is a multiple option.
155
+ #
156
+ # @return [Hash] the value and modifier, as a Hash, for a non-multiple
157
+ # option.
158
+ # @return [Array] the array of hashes containing value and modifiers, like
159
+ # those returned on a non-multiple options, for a multiple option.
160
+ def inspect
161
+ return @value.inspect
162
+ end
163
+
164
+ private
165
+
166
+ # Returns this SettingProxy's related Option based on the given key. This
167
+ # is necessary to ensure all configuration requirements are upheld during
168
+ # reads and writes.
169
+ #
170
+ # @return [ActiveConfiguration::Option] the option for this setting.
171
+ def option
172
+ return @manager.configurable.class.configuration.options[key]
173
+ end
174
+
175
+ # Coerces a stored String value into the expected type if an allowed format
176
+ # is explicitly set on this setting's option.
177
+ #
178
+ # Note: Because values are validated against given formats when updated,
179
+ # it is assumed that they can be properly coerced back to their intended
180
+ # type.
181
+ #
182
+ # @param [String] value the value stored in the database that must be coerced.
183
+ #
184
+ # @return [String, Fixnum, Float, TrueClass, FalseClass] the properly coerced
185
+ # value, assuming the value should be coerced.
186
+ def coerce(value)
187
+ if !value.nil? and !option.allowed_format.nil?
188
+ case option.allowed_format
189
+ when 'fixnum'
190
+ value = value.to_i
191
+ when 'float'
192
+ value = value.to_f
193
+ when 'boolean'
194
+ value = true if (value == 'true' or value == 't')
195
+ value = false if (value == 'false' or value == 'f')
196
+ end
197
+ end
198
+
199
+ return value
200
+ end
201
+ end
202
+ 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 = 0
5
+ PATCH = 0
6
+ BUILD = nil
7
+
8
+ STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
9
+ end
10
+ 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,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,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_configuration
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Thomas Mango
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-31 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rails
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 7
29
+ segments:
30
+ - 3
31
+ - 0
32
+ - 0
33
+ version: 3.0.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: sqlite3-ruby
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: active_configuration
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: rspec-rails
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ type: :development
77
+ version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ name: yard
80
+ prerelease: false
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ type: :development
91
+ version_requirements: *id005
92
+ - !ruby/object:Gem::Dependency
93
+ name: activerecord
94
+ prerelease: false
95
+ requirement: &id006 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 7
101
+ segments:
102
+ - 3
103
+ - 0
104
+ - 0
105
+ version: 3.0.0
106
+ type: :runtime
107
+ version_requirements: *id006
108
+ - !ruby/object:Gem::Dependency
109
+ name: activesupport
110
+ prerelease: false
111
+ requirement: &id007 !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 7
117
+ segments:
118
+ - 3
119
+ - 0
120
+ - 0
121
+ version: 3.0.0
122
+ type: :runtime
123
+ version_requirements: *id007
124
+ description: |-
125
+ ActiveConfiguration is an engine that exposes a generic settings store to
126
+ ActiveRecord models. Made for very configurable applications, it allows you
127
+ to avoid implementing specific ways to store settings for each model that
128
+ needs such configuration. If your application isn't very configurable,
129
+ ActiveConfiguration is probably overkill.
130
+ email: tsmango@gmail.com
131
+ executables: []
132
+
133
+ extensions: []
134
+
135
+ extra_rdoc_files:
136
+ - README.md
137
+ files:
138
+ - app/models/active_configuration/setting.rb
139
+ - lib/active_configuration.rb
140
+ - lib/active_configuration/base.rb
141
+ - lib/active_configuration/engine.rb
142
+ - lib/active_configuration/error.rb
143
+ - lib/active_configuration/option.rb
144
+ - lib/active_configuration/setting_manager.rb
145
+ - lib/active_configuration/setting_proxy.rb
146
+ - lib/active_configuration/table_name.rb
147
+ - lib/active_configuration/version.rb
148
+ - lib/active_record/configuration.rb
149
+ - lib/generators/active_configuration/install/install_generator.rb
150
+ - lib/generators/active_configuration/install/templates/create_active_configuration_settings.rb
151
+ - README.md
152
+ homepage: http://github.com/tsmango/active_configuration
153
+ licenses: []
154
+
155
+ post_install_message:
156
+ rdoc_options: []
157
+
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ hash: 3
166
+ segments:
167
+ - 0
168
+ version: "0"
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ hash: 3
175
+ segments:
176
+ - 0
177
+ version: "0"
178
+ requirements: []
179
+
180
+ rubyforge_project:
181
+ rubygems_version: 1.8.6
182
+ signing_key:
183
+ specification_version: 3
184
+ summary: A generic settings store for Rails 3.x ActiveRecord models.
185
+ test_files: []
186
+