Cockpit

Super DRY Settings for Ruby, Rails, and Sinatra Apps.

I am going to use this in all future gems that need configurable variables. Reason being, every gem uses some configurable variables, and you end up writing the same code over and over again.

This will make it so if say 10 gems have custom settings, you can change this:

Paperclip.config  = ...
S3.configure      = ...
Authlogic.setup   = ...
MyApp.settings    = ....

to this:

Settings do
  paperclip       ...
  s3              ...
  authentication  ...
  app             ...
end

Paperclip.config  = Settings(:paperclip)
S3.configure      = Settings(:s3)
Authlogic.setup   = Settings(:authentication)
MyApp.settings    = Settings(:app)

Which translates to 1, uniform, clean, configuration file.

Install

sudo gem install cockpit

Usage

Migration

create_table :settings, :force => true do |t|
  t.string :key
  t.string :value
  t.string :cast_as
  t.string :configurable_type
  t.integer :configurable_id
end

Setup (config/initializers/settings.rb)

Cockpit do
  site do
    title "Martini", :tooltip => "Set your title!"
    tagline "Developer Friendly, Client Ready Blog with Rails 3"
    keywords "Rails 3, Heroku, JQuery, HTML 5, Blog Engine, CSS3"
    copyright "© 2010 Viatropos. All rights reserved."
    timezones :value => lambda { TimeZone.first }, :options => lambda { TimeZone.all }
    date_format "%m %d, %Y"
    time_format "%H"
    week_starts_on "Monday", :options => ["Monday", "Sunday", "Friday"]
    language "en-US", :options => ["en-US", "de"]
    touch_enabled true
    touch_as_subdomain false
    google_analytics ""
    teasers :title => "Teasers" do
      disable false
      left 1, :title => "Left Teaser"
      right 2
      center 3
    end
    main_quote 1
  end
  asset :title => "Asset (and related) Settings" do
    thumb do
      width 100, :tip => "Thumb's width"
      height 100, :tip => "Thumb's height"
    end
    medium do
      width 600, :tip => "Thumb's width"
      height 250, :tip => "Thumb's height"
    end
    large do
      width 600, :tip => "Large's width"
      height 295, :tip => "Large's height"
    end
  end
  authentication :title => "Authentication Settings" do
    use_open_id true
    use_oauth true
  end
  front_page do
    slideshow_tag "slideshow"
    slideshow_effect "fade"
  end
  page do
    per_page 10
    feed_per_page 10
  end
  people do
    show_avatars true
    default_avatar "/images/missing-person.png"
  end
  social do
    facebook "http://facebook.com/viatropos"
    twitter "http://twitter.com/viatropos"
  end
  s3 do
    key "my_key"
    secret "my_secret"
  end
end

Get

Settings.get("site.title").value        #=> "Martini"
Settings.get("site.title.value")        #=> "Martini"
Settings("site.title").value            #=> "Martini"
Settings("site.title.value")            #=> "Martini"
Settings["site.title"].value            #=> "Martini"
Settings["site.title.value"]            #=> "Martini"
Settings.site.title.value               #=> "Martini" # doesn't pass through store yet

Set

Settings.set("site.title" => "Martini") #=> {:site => {:title => {:value => "Martini"}}}
Settings("site.title" => "Martini")     #=> {:site => {:title => {:value => "Martini"}}}
Settings["site.title"] = "Martini"      #=> {:site => {:title => {:value => "Martini"}}}
Settings.site.title = "Martini"         #=> {:site => {:title => {:value => "Martini"}}} # doesn't pass through store yet

Key points

  • Each node is any word you want
  • You can nest them arbitrarily deep
  • You can use Procs
  • Values are type casted
  • Settings can be defined in yaml or using the DSL.
  • The preferred way to get values is Settings("path.to.value").value
  • You can add custom properties to each setting:
    • Settings("site.title").tooltip #=> "Set your title!"
  • You have multiple storage options:
    • Settings.store = :db: Syncs setting to/from ActiveRecord
    • Settings.store = :memory: Stores everything in a Hash (memoized, super fast)
  • You can specify them on a per-model basis.

Example:

class User < ActiveRecord::Base
  acts_as_configurable :settings do
    name "Lance", :title => "First Name", :options => ["Lance", "viatropos"]
    favorite do
      color "red"
    end
  end
end

User.new.settings #=> <#Settings @tree={
  :favorite => {
    :color => {:type=>:string, :value=>"red"}
  },
  :name => {:type=>:string, :title=>"First Name", :value=>"Lance", :options=>["Lance", "Viatropos"]}
}/>

Why

There's no standard yet for organizing random properties in Rails apps. And settings should be able to be modified through an interface (think Admin panel).

Cockpit encapsulates the logic common to:

  • Options
  • Preferences
  • Settings
  • Configuration
  • Properties and Attributes
  • Key/Value stores

Sometimes you need a global store, sometimes that global store needs to be customizable by the user, sometimes each user has their own set of configurations. This handles all of those cases.

Todo

  • Add ability to freeze certain branches of the tree (so plugins can use it and know Settings.clear won't remove it)
  • Settings should be sorted by the way they were constructed
  • Check type, so when it is saved it knows what to do.
  • Store global declarations in memory
  • Create "context" for each set of settings, giving it its own tree. Allows mimicking subclasses.
  • Settings should be a collection of trees or contexts: Settings user global default user_a user_b widget global default widget_a widget_b text default widget_a widget_b social default widget_a widget_b Settings.for(:widget, :social) #=> default social widget settings.

This ended up being very similar to i18n:

I think the i18n gem should be broken down into two parts: Configuration (key/value store), and Translation.

End Goal

  • Base key-value functionality gem, which allows you to store arbitrary key values in any database (similar to moneta). Should store settings in MongoDB by default.
  • i18n and Cockpit build on top of that

Alternatives