sandyw-active_configuration 1.1.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,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: []