active_configuration 1.0.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,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
+