iron-settings 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/History.txt +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +133 -0
- data/Version.txt +1 -0
- data/db/settings_migration.rb +23 -0
- data/lib/iron/rake_loader.rb +6 -0
- data/lib/iron/settings.rb +161 -0
- data/lib/iron/settings/builder.rb +72 -0
- data/lib/iron/settings/class_level.rb +122 -0
- data/lib/iron/settings/cursor.rb +170 -0
- data/lib/iron/settings/db_store.rb +57 -0
- data/lib/iron/settings/db_value.rb +33 -0
- data/lib/iron/settings/entry.rb +26 -0
- data/lib/iron/settings/group.rb +102 -0
- data/lib/iron/settings/instance_level.rb +98 -0
- data/lib/iron/settings/node.rb +46 -0
- data/lib/iron/settings/root.rb +15 -0
- data/lib/iron/settings/static_store.rb +85 -0
- data/lib/iron/settings/value_store.rb +88 -0
- data/lib/tasks/settings.rake +65 -0
- data/spec/samples/static-test +4 -0
- data/spec/samples/static-test-2 +1 -0
- data/spec/settings/builder_spec.rb +48 -0
- data/spec/settings/class_level_spec.rb +46 -0
- data/spec/settings/cursor_spec.rb +96 -0
- data/spec/settings/db_store_spec.rb +12 -0
- data/spec/settings/db_value_spec.rb +58 -0
- data/spec/settings/entry_spec.rb +34 -0
- data/spec/settings/group_spec.rb +35 -0
- data/spec/settings/instance_level_spec.rb +44 -0
- data/spec/settings/settings_spec.rb +61 -0
- data/spec/settings/static_store_spec.rb +46 -0
- data/spec/settings/value_store_spec.rb +55 -0
- data/spec/spec_helper.rb +50 -0
- metadata +114 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require <%= File.join(File.expand_path(File.dirname(__FILE__)), 'spec', 'spec_helper.rb') %>
|
data/History.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
== 1.0.0 / 2013-09-XX
|
2
|
+
|
3
|
+
* Initial revision
|
4
|
+
* Working class and instance level settings definition
|
5
|
+
* Working static and db-backed value stores
|
6
|
+
* Working user-specifiable settings data types
|
7
|
+
* Reload support for file timestamp, timeout, and custom proc-based reload logic
|
8
|
+
* Security checking for file ownership and world-writability on settings files
|
9
|
+
* Working Rails integration
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Irongaze Consulting LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
= GEM: iron-settings
|
2
|
+
|
3
|
+
Written by Rob Morris @ Irongaze Consulting LLC (http://irongaze.com)
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
A set of classes to support elegant class and instance level settings, both
|
8
|
+
static (in files/code) and dynamic (database-backed).
|
9
|
+
|
10
|
+
== SYNOPSIS
|
11
|
+
|
12
|
+
Managing settings in applications is a pain. This gem makes it less so. You define your setting
|
13
|
+
structure using a simple DSL, then override it as needed in your code or via user interaction. Great for
|
14
|
+
gem-based tools, frameworks, etc needing elegant customization, and great for any project
|
15
|
+
wanting to have flexible, powerful user-editable settings stored in a database.
|
16
|
+
|
17
|
+
As an example, consider the User model in any given Rails-based site. Here's how we could add
|
18
|
+
some flexible settings:
|
19
|
+
|
20
|
+
class User < ActiveRecord::Base
|
21
|
+
|
22
|
+
# Declare our settings schema for this model with types, names and defaults.
|
23
|
+
# Each model instance will have its own set of values for these settings.
|
24
|
+
instance_settings do
|
25
|
+
# homepage is a string, with a default of '/dashboard'
|
26
|
+
string('homepage', '/dashboard')
|
27
|
+
|
28
|
+
# Groups let you bundle settings together, as well as namespace
|
29
|
+
# items if so desired
|
30
|
+
group('notification') do
|
31
|
+
# This setting has a dynamic default - basically a block that gets evaluated to generate
|
32
|
+
# a default value. It has access to the model that owns it - in this case, we have
|
33
|
+
# an email notification address that defaults to the user's primary email.
|
34
|
+
string('email') {|user| user.email}
|
35
|
+
|
36
|
+
# Lists (aka arrays) are also supported, of any of the supported types
|
37
|
+
int_list('subscriptions', [ListServe::NEWS_GROUP, ListServe::ALERTS])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
To work with these settings:
|
44
|
+
|
45
|
+
# Create a new user
|
46
|
+
>> @user = User.new(:email => 'info@irongaze.com')
|
47
|
+
|
48
|
+
# Default values are available immediately
|
49
|
+
>> puts @user.settings.homepage
|
50
|
+
=> '/dashboard'
|
51
|
+
|
52
|
+
# Override the default value for this user
|
53
|
+
>> @user.settings.homepage = '/dashboard-v2'
|
54
|
+
|
55
|
+
# Update his notification frequency, drilling down into the 'notification' group
|
56
|
+
>> @user.settings.notification.frequency = 10
|
57
|
+
|
58
|
+
# Settings are saved when the model that owns them is saved
|
59
|
+
>> @user.save!
|
60
|
+
|
61
|
+
# Once saved, the settings are reloaded as needed
|
62
|
+
>> puts User.find_by_email('info@irongaze.com').settings.homepage
|
63
|
+
=> '/dashboard-v2'
|
64
|
+
|
65
|
+
Static settings work differently. Imagine you are building a command-line tool that needs
|
66
|
+
configuration info.
|
67
|
+
|
68
|
+
class MyTool
|
69
|
+
|
70
|
+
# You'd first define your schema at class level, passing the file
|
71
|
+
# you want to use as an option on creation
|
72
|
+
class_settings(:file => '~/.mytool') do
|
73
|
+
string('api_key')
|
74
|
+
string('base_path', '~')
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize
|
78
|
+
# The bound file will be loaded automatically if present on first access.
|
79
|
+
# Verify we have what we need - interrogative version of keys tests for the
|
80
|
+
# presence of a non-default value.
|
81
|
+
unless MyTool.settings.api_key?
|
82
|
+
raise "You must define your API key in your ~/.mytool settings file!"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
Now, your users could create a .mytool file like so:
|
89
|
+
|
90
|
+
api_key '1234ASDF'
|
91
|
+
base_path '~/code'
|
92
|
+
|
93
|
+
On calling MyTool.settings, this file would be loaded and override the settings defaults.
|
94
|
+
|
95
|
+
You could set up a similar arrangement for managing settings for Gems and other reusable libraries.
|
96
|
+
|
97
|
+
== LIMITATIONS
|
98
|
+
|
99
|
+
Database-backed settings are not a panacea. They duplicate functionality that could be built more
|
100
|
+
directly using standard model attributes. In particular, care must be taken to avoid changing existing
|
101
|
+
entry paths and data types, as doing so will invalidate saved values and potentially cause errors
|
102
|
+
on loading prior values.
|
103
|
+
|
104
|
+
In addition, they are not intended for storing hundreds of thousands of values! Like any key/value
|
105
|
+
store, they are a tool suited for certain tasks.
|
106
|
+
|
107
|
+
== REQUIREMENTS
|
108
|
+
|
109
|
+
Depends on the iron-extensions gem, and optionally requires ActiveRecord to support db-backed
|
110
|
+
dynamic settings.
|
111
|
+
|
112
|
+
Requires RSpec, ActiveRecord and Sqlite3 gems to build/test.
|
113
|
+
|
114
|
+
== INSTALLATION
|
115
|
+
|
116
|
+
To install, simply run:
|
117
|
+
|
118
|
+
sudo gem install iron-settings
|
119
|
+
|
120
|
+
RVM users can skip the sudo:
|
121
|
+
|
122
|
+
gem install iron-settings
|
123
|
+
|
124
|
+
Then use
|
125
|
+
|
126
|
+
require 'iron/settings'
|
127
|
+
|
128
|
+
to require the library code.
|
129
|
+
|
130
|
+
If you want to use db-backed settings (for example, for per-model settings), you will need to run
|
131
|
+
the settings-creation migration. In a Rails project, simply run the provided rake settings:install task,
|
132
|
+
which will copy the required migration into your app. If you're using this in a non-Rails environment,
|
133
|
+
you can manually run the migration in <gem_path>/db/settings_migration.rb.
|
data/Version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class SettingsMigration < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def change
|
4
|
+
|
5
|
+
# To support db-backed settings, we need a table containing their values
|
6
|
+
create_table :settings_values do |t|
|
7
|
+
# Polymorphic ownership as "context"
|
8
|
+
t.string 'context_type', :null => false
|
9
|
+
t.integer 'context_id'
|
10
|
+
|
11
|
+
# Full key, ie App.settings.foo.bar.some_value => 'foo.bar.some_value'
|
12
|
+
t.string 'full_key', :null => false
|
13
|
+
|
14
|
+
# Serialized value
|
15
|
+
t.text 'value'
|
16
|
+
end
|
17
|
+
|
18
|
+
# In cases where we have a lot of db-backed settings, an index is a must!
|
19
|
+
add_index :settings_values, ['context_type', 'context_id']
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# Dependencies
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'iron/extensions'
|
5
|
+
|
6
|
+
# Top-level class with numerous helper methods for defining and handling
|
7
|
+
# our supported settings data types.
|
8
|
+
class Settings
|
9
|
+
|
10
|
+
# Registers a new data type for use in settings entries. Pass a symbol
|
11
|
+
# for the type, and a lambda that accepts an arbitrary value and either
|
12
|
+
# parses it into a value of the required type or raises.
|
13
|
+
#
|
14
|
+
# For example, let's say we had a project that commonly had to assign
|
15
|
+
# admin users (represented here by ActiveRecord models) on projects, tasks, etc.
|
16
|
+
# We could create a :user data type that would seamlessly allow setting
|
17
|
+
# and getting admin users as a native settings type:
|
18
|
+
#
|
19
|
+
# Settings.register_type :user,
|
20
|
+
# :parse => lambda {|val| val.is_a?(AdminUser) ? val.id : raise },
|
21
|
+
# :restore => lambda {|val| AdminUser.find_by_id(val) }
|
22
|
+
#
|
23
|
+
# Now we can use the user type in our settings definitions:
|
24
|
+
#
|
25
|
+
# class Project < ActiveRecord::Base
|
26
|
+
# instance_settings do
|
27
|
+
# user('lead')
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# With our settings defined, we can get and set admin users to that setting entry:
|
32
|
+
#
|
33
|
+
# @project = Project.new(:name => 'Lazarus')
|
34
|
+
# @project.settings.lead = AdminUser.find_by_email('jim@example.com')
|
35
|
+
# @project.save
|
36
|
+
#
|
37
|
+
# @project = Project.find_by_name('Lazarus')
|
38
|
+
# # Will use our :restore lambda to restore the id as a full AdminUser model
|
39
|
+
# @lead = @project.settings.lead
|
40
|
+
# # Will print 'jim@example.com'
|
41
|
+
# puts @lead.email
|
42
|
+
#
|
43
|
+
# If you do not define either the parse or restore lambdas, they will act as
|
44
|
+
# pass-throughs. Also note that you do not need to handle nil values, which
|
45
|
+
# will always parse to nil and restore to nil.
|
46
|
+
def self.register_type(type, options = {})
|
47
|
+
data_type_map[type] = {parse: options[:parse], restore: options[:restore]}
|
48
|
+
end
|
49
|
+
|
50
|
+
# Registers initial set of built-in data types
|
51
|
+
def self.register_built_ins
|
52
|
+
register_type(:int, parse: lambda {|val| val.is_a?(Fixnum) || (val.is_a?(String) && val.integer?) ? val.to_i : raise })
|
53
|
+
register_type(:string, parse: lambda {|val| val.is_a?(String) ? val : raise })
|
54
|
+
register_type(:symbol, parse: lambda {|val| val.is_a?(Symbol) ? val : raise })
|
55
|
+
register_type(:bool, parse: lambda {|val| (val === true || val === false) ? val : raise })
|
56
|
+
register_type(:var)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.data_type_map
|
60
|
+
@data_type_map ||= {}
|
61
|
+
@data_type_map
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns array of symbols for the supported data types for
|
65
|
+
# settings entries. You can add custom types using the #register_type
|
66
|
+
# method
|
67
|
+
def self.data_types
|
68
|
+
data_type_map.keys
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the proper parser for a given type and mode (either :parse or :restore)
|
72
|
+
def self.converter_for(type, mode)
|
73
|
+
if type.to_s.ends_with?('_list')
|
74
|
+
type = type.to_s.gsub('_list','').to_sym
|
75
|
+
end
|
76
|
+
|
77
|
+
hash = data_type_map[type]
|
78
|
+
raise ArgumentError.new("Unknown settings data type [#{type.inspect}]") if hash.nil?
|
79
|
+
hash[mode]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.parse(val, type)
|
83
|
+
# Nil is always ok
|
84
|
+
return nil if val.nil?
|
85
|
+
|
86
|
+
# Check for lists
|
87
|
+
parser = converter_for(type, :parse)
|
88
|
+
if type.to_s.ends_with?('_list')
|
89
|
+
# Gotta be an array, thanks
|
90
|
+
raise ArgumentError.new("Must set #{type} settings to an array of values") unless val.is_a?(Array)
|
91
|
+
|
92
|
+
# Parse 'em all
|
93
|
+
return val if parser.nil?
|
94
|
+
val.collect {|v| parser.call(v) } rescue raise ArgumentError.new("Values #{val.inspect} is not a valid #{type}")
|
95
|
+
else
|
96
|
+
# Single value
|
97
|
+
return val if parser.nil?
|
98
|
+
parser.call(val) rescue raise ArgumentError.new("Value [#{val.inspect}] is not a valid #{type}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.restore(val, type)
|
103
|
+
# Nil restores to nil... always
|
104
|
+
return nil if val.nil?
|
105
|
+
|
106
|
+
# Check for lists
|
107
|
+
restorer = converter_for(type, :restore)
|
108
|
+
if type.to_s.ends_with?('_list')
|
109
|
+
# Gotta be an array, thanks
|
110
|
+
raise ArgumentError.new("Must set #{type} settings to an array of values") unless val.is_a?(Array)
|
111
|
+
|
112
|
+
# Parse 'em all
|
113
|
+
return val if restorer.nil?
|
114
|
+
val.collect {|v| parser.call(v) } rescue raise ArgumentError.new("Unable to restore values #{val.inspect} to type #{type}")
|
115
|
+
else
|
116
|
+
# Single value
|
117
|
+
return val if restorer.nil?
|
118
|
+
restorer.call(val) rescue raise ArgumentError.new("Unable to restore value [#{val.inspect}] to type #{type}")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.classes
|
123
|
+
@classes ||= []
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.default_timestamp_file(class_name)
|
127
|
+
filename = class_name.gsub(/([a-z])([A-Z])/, '\1-\2').to_dashcase + '-settings.txt'
|
128
|
+
defined?(Rails) ?
|
129
|
+
File.join(RAILS_ROOT, 'tmp', filename) :
|
130
|
+
File.join(Dir.tmpdir, filename)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
# Register our built-in types
|
136
|
+
Settings.register_built_ins
|
137
|
+
|
138
|
+
# Include required classes
|
139
|
+
require_relative 'settings/node'
|
140
|
+
require_relative 'settings/group'
|
141
|
+
require_relative 'settings/root'
|
142
|
+
require_relative 'settings/entry'
|
143
|
+
require_relative 'settings/builder'
|
144
|
+
require_relative 'settings/cursor'
|
145
|
+
require_relative 'settings/class_level'
|
146
|
+
require_relative 'settings/instance_level'
|
147
|
+
require_relative 'settings/value_store'
|
148
|
+
require_relative 'settings/static_store'
|
149
|
+
if defined?(ActiveRecord)
|
150
|
+
require_relative 'settings/db_value'
|
151
|
+
require_relative 'settings/db_store'
|
152
|
+
end
|
153
|
+
|
154
|
+
# Install our support at the correct scopes
|
155
|
+
Module.send(:include, Settings::ClassLevel)
|
156
|
+
Object.send(:include, Settings::InstanceLevel)
|
157
|
+
|
158
|
+
# Rails support here
|
159
|
+
if defined?(Rails)
|
160
|
+
require_relative 'rake_loader'
|
161
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class Settings
|
2
|
+
|
3
|
+
# Mirror to the Cursor class, this class helps extend and expand a settings
|
4
|
+
# hierarchy.
|
5
|
+
class Builder
|
6
|
+
|
7
|
+
def self.define(group, &block)
|
8
|
+
builder = self.new(group)
|
9
|
+
builder.define(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Bind to a group/root
|
13
|
+
def initialize(group)
|
14
|
+
@group = group
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define in block mode
|
18
|
+
def define(&block)
|
19
|
+
DslProxy.exec(self, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Create a new sub-group, yield for definition if block passed
|
23
|
+
def group(name, &block)
|
24
|
+
verify_key?(name)
|
25
|
+
group = @group.find_group(name)
|
26
|
+
unless group
|
27
|
+
verify_available?(name, :group)
|
28
|
+
group = @group.add_group(name)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Chain it
|
32
|
+
builder = self.class.new(group)
|
33
|
+
builder.define(&block) if block
|
34
|
+
builder
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(method, *args, &block)
|
38
|
+
type = method.to_s
|
39
|
+
|
40
|
+
if Settings.data_types.include?(type.gsub('_list','').to_sym)
|
41
|
+
type = type.to_sym
|
42
|
+
name = args[0]
|
43
|
+
default = args[1]
|
44
|
+
verify_key?(name)
|
45
|
+
verify_available?(name, :entry)
|
46
|
+
@group.add_entry(name, type, default, &block)
|
47
|
+
else
|
48
|
+
super
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def respond_to_missing?(method, include_private = false)
|
53
|
+
Settings.data_types.include?(method.to_s.gsub('_list','').to_sym)
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
# Raise's if name already in use for the group
|
59
|
+
def verify_available?(name, type)
|
60
|
+
unless @group.find_item(name).nil?
|
61
|
+
raise RuntimeError.new("#{type.capitalize}'s name '#{name}' already defined for settings group: #{@group.key}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def verify_key?(key)
|
66
|
+
unless key.is_a?(String) && key.match(/^[a-z][a-z0-9_]*$/)
|
67
|
+
raise RuntimeError.new("Key '#{key}' is not a valid group/entry key while defining settings group #{@group.key} - must be a string with a-z, 0-9, or _ chars")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
class Settings #:nodoc:
|
2
|
+
|
3
|
+
# Class-level settings can be either static (file/code-based) or dynamic (db-based) depending
|
4
|
+
# on your needs. Static settings will work well for gem configuration, command-line tools,
|
5
|
+
# etc. while dynamic settings might be useful for a CMS or other web-based tool that needs
|
6
|
+
# to support user editing of settings values on-the-fly.
|
7
|
+
module ClassLevel
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
# Access the class-level settings values for this class, returns
|
12
|
+
# a Settings::Cursor to read/write, pointed at the root of the
|
13
|
+
# settings definition for this class.
|
14
|
+
#
|
15
|
+
# Optionally accepts a block
|
16
|
+
# for mass assignment using DSL setters, eg:
|
17
|
+
#
|
18
|
+
# Foo.settings do
|
19
|
+
# some_group.some_entry 'some value'
|
20
|
+
# some_other_entry 250
|
21
|
+
# end
|
22
|
+
def settings(&block)
|
23
|
+
@settings_values.reload_if_needed
|
24
|
+
cursor = Settings::Cursor.new(@settings_class_root, @settings_values)
|
25
|
+
DslProxy::exec(cursor, &block) if block
|
26
|
+
cursor
|
27
|
+
end
|
28
|
+
|
29
|
+
# Reset state to default values only - useful in testing
|
30
|
+
def class_settings_reset!
|
31
|
+
@settings_values = @settings_class_options[:store] == :static ?
|
32
|
+
Settings::StaticStore.new(@settings_class_root, @settings_class_options) :
|
33
|
+
Settings::DbStore.new(@settings_class_root, @settings_class_options)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Force a settings reload (from db or file(s) depending on settings) regarless
|
37
|
+
# of need to reload automatically. Useful for testing, but not generally needed in production use
|
38
|
+
def reload_settings
|
39
|
+
@settings_values.load
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define the class-level settings for a given class. Supported options include:
|
45
|
+
#
|
46
|
+
# :store => :static | :dynamic - how to load and (potentially) save settings values, defaults to :static
|
47
|
+
#
|
48
|
+
# Static mode options:
|
49
|
+
#
|
50
|
+
# :file => '/path/to/file' - provides a single file to load when using the static settings store
|
51
|
+
# :files => ['/path1', '/path2'] - same as :file, but allows multiple files to be loaded in order
|
52
|
+
#
|
53
|
+
# Reload timing (primarily intended for use with :db mode):
|
54
|
+
#
|
55
|
+
# :reload => <when> - when and if to reload from file/db, with <when> as one of:
|
56
|
+
# true - on every access to #settings
|
57
|
+
# false - only do initial load, never reload
|
58
|
+
# '/path/to/file' - file to test for modified timestamp changes, reload if file timestamp is after latest load
|
59
|
+
# <num seconds> - after N seconds since last load
|
60
|
+
# lambda { <true to reload> } - custom lambda to execute to check for reload, reloads on returned true value
|
61
|
+
#
|
62
|
+
# Any options passed on subsequent calls to #class_settings will be ignored.
|
63
|
+
#
|
64
|
+
# A passed block will be evaluated in the context of a Settings::Builder instance
|
65
|
+
# that can be used to define groups and entries.
|
66
|
+
#
|
67
|
+
# Example:
|
68
|
+
#
|
69
|
+
# class Site
|
70
|
+
# class_settings(:file => File.join(RAILS_ROOT, 'config/site-settings.rb')) do
|
71
|
+
# string('name')
|
72
|
+
# group('security') do
|
73
|
+
# bool('force-ssl', false)
|
74
|
+
# string('secret-key')
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# The above would set up Site.settings.name, Site.settings.security.force_ssl, etc, with an optional settings
|
80
|
+
# file located at $RAILS_ROOT/config/site-settings.rb
|
81
|
+
def class_settings(options = {}, &block)
|
82
|
+
unless @settings_class_root
|
83
|
+
# Set up our root group and options
|
84
|
+
@settings_class_root = Settings::Root.new()
|
85
|
+
options = {
|
86
|
+
:store => :static
|
87
|
+
}.merge(options)
|
88
|
+
|
89
|
+
# Set our default reload timing
|
90
|
+
if options[:reload].nil?
|
91
|
+
if options[:store] == :static
|
92
|
+
# Static settings generally don't need reloading
|
93
|
+
options[:reload] = false
|
94
|
+
else
|
95
|
+
# For dynamic, db-backed settings at the class level, we use
|
96
|
+
# file modified reload timing by default
|
97
|
+
options[:reload] = Settings.default_timestamp_file(self.name)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Save off our options
|
102
|
+
@settings_class_options = options
|
103
|
+
|
104
|
+
# Add this class to the settings registry
|
105
|
+
Settings.classes << self
|
106
|
+
|
107
|
+
# Add in support for settings for this class
|
108
|
+
extend ClassMethods
|
109
|
+
|
110
|
+
# Create our value store
|
111
|
+
class_settings_reset!
|
112
|
+
end
|
113
|
+
|
114
|
+
# Create a builder and do the right thing based on passed args
|
115
|
+
builder = Settings::Builder.new(@settings_class_root)
|
116
|
+
builder.define(&block) if block
|
117
|
+
builder
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|