iron-settings 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|