Super DRY Settings for Ruby, Rails, and Sinatra Apps with pluggable backend support.
Install
sudo gem install cockpit
How it works
You can define arbitrarily nested key/value pairs of any type, and customize them from an Admin panel or the terminal, and save them to the MySQL, MongoDB, Redis, in memory, or even a File.
- Settings can be associated with a model class
- Settings can be associated with a model instance, which can use the model class settings as defaults
- Settings can be global and not reference a model at all (basic key/value store).
You define settings like this:
Cockpit :active_record do
site do
title "My Site"
time_zone :default => lambda { "Hawaii" }
feed do
per_page 10
formats %w(rss atom)
end
end
end
That gives you an instance of Cockpit::Settings
, a tree data structure.
Global Settings API
If you've defined your :active_record
settings like above, which are global settings, you can use them like this:
Get Methods
Cockpit::Settings["site.feed.per_page"] #=> 10
Cockpit::Settings("site.feed.per_page") #=> 10
Cockpit::Settings.site.feed.per_page.value #=> 10
Everything ultimately passes through the hash form of the method, Cockpit::Settings["path"]
, so that's the most optimized way to do it.
You can also check to see if these settings exist:
Cockpit::Settings.site.feed.per_page? #=> true
Set Methods
Cockpit::Settings["site.feed.per_page"] = 20
Cockpit::Settings("site.feed.per_page", 20)
Cockpit::Settings.site.feed.per_page.value = 20
Behind the Scenes
When you define settings using the DSL, they get stored as Cockpit::Spec
objects into a global hash in the Cockpit::Settings
class, which is a dictionary of specs[class][name] = spec
. Global specs aren't associated with a class (e.g. model class), so the class
is NilClass
. You can have multiple global settings classes if you'd like, just give them names:
Cockpit :store => :active_record, :name => :more_settings do
hello "world"
end
You can access specific global settings like this:
Cockpit::Settings.find(:more_settings).hello.value #=> "world"
Instance Settings API
You can also associate settings with any object (plain Object, ActiveRecord, MongoMapper::Document, etc.):
class User < ActiveRecord::Base
include Cockpit
cockpit do
preferences do
favorite_color "red"
end
settings do
birthday :after => :queue_birthday_message, :if => lambda { |key, value|
value =~ /\d\d\/\d\d\/\d\d\d\d/ # 10/03/1986
}
number_of_children, Integer
end
end
def queue_birthday_message(key, value)
BirthdayMailer.enqueue(value # ,...)
end
end
And access them like this:
user = User.new
user.cockpit["settings.number_of_children"] #=> 300
user.cockpit["preferences.favorite_color"] = "green"
user.cockpit.settings.number_of_children.value = 0
user.cockpit.preferences? #=> true
If your model class doesn't have methods named after the cockpit keys, it will generate methods for you and delegate them to the cockpit
:
user.preferences.favorite_color? #=> true
user.settings? #=> true
user.preferences.favorite_color.value = "turquoise"
Swappable Backend
The current backends supported are these keys:
- active_record
- mongo
- memory
Soon, or as need be, I'll support redis, files, couchdb, etc. Haven't needed them yet.
Caching
For Active Record, Cockpit just adds a has_many :settings
declaration to your model, and loads all of the settings on the first call, caching any further gets to settings for that model. This means basically that settings are extendable database attributes for your model.
Use Cases
This makes it really easy to edit random settings from an interface, such as an admin panel. Next goal is to add callbacks around save/destroy so you can run processes when settings are changed (such as changing your google_analytics, which would require re-rendering views if they were cached).
The goal is to make this enormous configuration dsl work, so I can define an entire site in a DSL.
Other API Notes
When you specify the DSL, that creates a flat tree of defaults, which aren't saved to the database. Then when you update the setting, it saves to the database, otherwise when the value is read and is null, it will use the default from the in-memory/dsl-defined tree.
You can also associate a hash with each setting definition. This is great for say options, defaults, titles and tooltips, etc. Here's an example:
Cockpit :active_record do
site do
time_zones "MST", :options => Proc.new { TZInfo::Timezone.all.map(&:name) }
end
end
assert_equal TZInfo::Timezone.all.map(&:name), Cockpit::Settings["site.time_zones"][:options].call
And you can access the definition object directly:
Cockpit::Settings.definition("site.time_zones").attributes[:options]
You can even do this in the terminal:
irb -r 'rubygems'
require 'cockpit'
Cockpit { site { title "Lance" } }
puts Cockpit::Settings["site.title"] #=> "Lance"
copyright @viatropos 2010