cockpit 0.0.1.5

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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Lance Pollard (lancejpollard@gmail.com)
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
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,175 @@
1
+ # Cockpit
2
+
3
+ <q>Super DRY Settings for Ruby, Rails, and Sinatra Apps.</q>
4
+
5
+ ## Install
6
+
7
+ sudo gem install cockpit
8
+
9
+ ## Usage
10
+
11
+ ### Migration
12
+
13
+ create_table :settings, :force => true do |t|
14
+ t.string :key
15
+ t.string :value
16
+ t.string :cast_as
17
+ t.string :configurable_type
18
+ t.integer :configurable_id
19
+ end
20
+
21
+ ### Setup (`config/initializers/settings.rb`)
22
+
23
+ Cockpit do
24
+ site do
25
+ title "Martini", :tooltip => "Set your title!"
26
+ tagline "Developer Friendly, Client Ready Blog with Rails 3"
27
+ keywords "Rails 3, Heroku, JQuery, HTML 5, Blog Engine, CSS3"
28
+ copyright "© 2010 Viatropos. All rights reserved."
29
+ timezones :value => lambda { TimeZone.first }, :options => lambda { TimeZone.all }
30
+ date_format "%m %d, %Y"
31
+ time_format "%H"
32
+ week_starts_on "Monday", :options => ["Monday", "Sunday", "Friday"]
33
+ language "en-US", :options => ["en-US", "de"]
34
+ touch_enabled true
35
+ touch_as_subdomain false
36
+ google_analytics ""
37
+ teasers :title => "Teasers" do
38
+ disable false
39
+ left 1, :title => "Left Teaser"
40
+ right 2
41
+ center 3
42
+ end
43
+ main_quote 1
44
+ end
45
+ asset :title => "Asset (and related) Settings" do
46
+ thumb do
47
+ width 100, :tip => "Thumb's width"
48
+ height 100, :tip => "Thumb's height"
49
+ end
50
+ medium do
51
+ width 600, :tip => "Thumb's width"
52
+ height 250, :tip => "Thumb's height"
53
+ end
54
+ large do
55
+ width 600, :tip => "Large's width"
56
+ height 295, :tip => "Large's height"
57
+ end
58
+ end
59
+ authentication :title => "Authentication Settings" do
60
+ use_open_id true
61
+ use_oauth true
62
+ end
63
+ front_page do
64
+ slideshow_tag "slideshow"
65
+ slideshow_effect "fade"
66
+ end
67
+ page do
68
+ per_page 10
69
+ feed_per_page 10
70
+ end
71
+ people do
72
+ show_avatars true
73
+ default_avatar "/images/missing-person.png"
74
+ end
75
+ social do
76
+ facebook "http://facebook.com/viatropos"
77
+ twitter "http://twitter.com/viatropos"
78
+ end
79
+ s3 do
80
+ key "my_key"
81
+ secret "my_secret"
82
+ end
83
+ end
84
+
85
+ #### Get
86
+
87
+ Settings.get("site.title").value #=> "Martini"
88
+ Settings.get("site.title.value") #=> "Martini"
89
+ Settings("site.title").value #=> "Martini"
90
+ Settings("site.title.value") #=> "Martini"
91
+ Settings["site.title"].value #=> "Martini"
92
+ Settings["site.title.value"] #=> "Martini"
93
+ Settings.site.title.value #=> "Martini" # doesn't pass through store yet
94
+
95
+ #### Set
96
+
97
+ Settings.set("site.title" => "Martini") #=> {:site => {:title => {:value => "Martini"}}}
98
+ Settings("site.title" => "Martini") #=> {:site => {:title => {:value => "Martini"}}}
99
+ Settings["site.title"] = "Martini" #=> {:site => {:title => {:value => "Martini"}}}
100
+ Settings.site.title = "Martini" #=> {:site => {:title => {:value => "Martini"}}} # doesn't pass through store yet
101
+
102
+ ### Key points
103
+
104
+ - Each node is any word you want
105
+ - You can nest them arbitrarily deep
106
+ - You can use Procs
107
+ - Values are type casted
108
+ - Settings can be defined in yaml or using the DSL.
109
+ - The preferred way to _get_ values is `Settings("path.to.value").value`
110
+ - You can add custom properties to each setting:
111
+ - `Settings("site.title").tooltip #=> "Set your title!"`
112
+ - You have multiple storage options:
113
+ - `Settings.store = :db`: Syncs setting to/from ActiveRecord
114
+ - `Settings.store = :memory`: Stores everything in a Hash (memoized, super fast)
115
+ - You can specify them on a per-model basis.
116
+
117
+ Example:
118
+
119
+ class User < ActiveRecord::Base
120
+ acts_as_configurable :settings do
121
+ name "Lance", :title => "First Name", :options => ["Lance", "viatropos"]
122
+ favorite do
123
+ color "red"
124
+ end
125
+ end
126
+ end
127
+
128
+ User.new.settings #=> <#Settings @tree={
129
+ :favorite => {
130
+ :color => {:type=>:string, :value=>"red"}
131
+ },
132
+ :name => {:type=>:string, :title=>"First Name", :value=>"Lance", :options=>["Lance", "Viatropos"]}
133
+ }/>
134
+
135
+ ### Why
136
+
137
+ 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).
138
+
139
+ Cockpit encapsulates the logic common to:
140
+
141
+ - Options
142
+ - Preferences
143
+ - Settings
144
+ - Configuration
145
+ - Properties and Attributes
146
+ - Key/Value stores
147
+
148
+ 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.
149
+
150
+ ## Todo
151
+
152
+ - Settings should be sorted by the way they were constructed
153
+ - Check type, so when it is saved it knows what to do.
154
+
155
+ This ended up being very similar to i18n:
156
+
157
+ - [http://guides.rubyonrails.org/i18n.html](http://guides.rubyonrails.org/i18n.html)
158
+ - [I asked about this on the i18n lighthouse](http://i18n.lighthouseapp.com/projects/14947/tickets/21-abstract-out-configuration-functionality-from-i18n-into-separate-gem#ticket-21-1)
159
+
160
+ I think the i18n gem should be broken down into two parts: Configuration (key/value store), and Translation.
161
+
162
+ #### End Goal
163
+
164
+ - Base key-value functionality gem, which allows you to store arbitrary key values in any database (similar to moneta).
165
+ - i18n and Cockpit build on top of that
166
+
167
+ ### Alternatives
168
+
169
+ - [Preferences](http://github.com/pluginaweek/preferences)
170
+ - [SettingsGoo](http://rubygems.org/gems/settings-goo)
171
+ - [RailsSettings](http://github.com/Squeegy/rails-settings)
172
+ - [SimpleConfig](http://github.com/lukeredpath/simpleconfig)
173
+ - [Configatron](http://github.com/markbates/configatron)
174
+ - [RConfig](http://github.com/rahmal/rconfig)
175
+ - [Serenity](http://github.com/progressions/serenity)
data/Rakefile ADDED
@@ -0,0 +1,78 @@
1
+ require 'rake'
2
+ require "rake/rdoctask"
3
+ require 'rake/gempackagetask'
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.name = "cockpit"
7
+ s.authors = ["Lance Pollard"]
8
+ s.version = "0.0.1.5"
9
+ s.summary = "Cockpit: Super DRY Configuration for Ruby, Rails, and Sinatra Apps"
10
+ s.homepage = "http://github.com/viatropos/cockpit"
11
+ s.email = "lancejpollard@gmail.com"
12
+ s.description = "Super DRY Configuration for Ruby, Rails, and Sinatra Apps"
13
+ s.has_rdoc = false
14
+ s.rubyforge_project = "cockpit"
15
+ s.platform = Gem::Platform::RUBY
16
+ s.files = %w(README.markdown Rakefile init.rb MIT-LICENSE) + Dir["{lib,rails,test}/**/*"] - Dir["test/tmp"]
17
+ s.require_path = "lib"
18
+ end
19
+
20
+ Rake::GemPackageTask.new(spec) do |pkg|
21
+ pkg.gem_spec = spec
22
+ pkg.package_dir = "pkg"
23
+ end
24
+
25
+ desc 'run unit tests'
26
+ task :test do
27
+ Dir["test/**/*"].each do |file|
28
+ next unless File.basename(file) =~ /test_/
29
+ next unless File.extname(file) == ".rb"
30
+ system "ruby #{file}"
31
+ end
32
+ end
33
+
34
+ desc "Create .gemspec file (useful for github)"
35
+ task :gemspec do
36
+ File.open("pkg/#{spec.name}.gemspec", "w") do |f|
37
+ f.puts spec.to_ruby
38
+ end
39
+ end
40
+
41
+ desc "Build the gem into the current directory"
42
+ task :gem => :gemspec do
43
+ `gem build pkg/#{spec.name}.gemspec`
44
+ end
45
+
46
+ desc "Publish gem to rubygems"
47
+ task :publish => [:package] do
48
+ %x[gem push pkg/#{spec.name}-#{spec.version}.gem]
49
+ end
50
+
51
+ desc "Print a list of the files to be put into the gem"
52
+ task :manifest do
53
+ File.open("Manifest", "w") do |f|
54
+ spec.files.each do |file|
55
+ f.puts file
56
+ end
57
+ end
58
+ end
59
+
60
+ desc "Install the gem locally"
61
+ task :install => [:package] do
62
+ File.mkdir("pkg") unless File.exists?("pkg")
63
+ command = "gem install pkg/#{spec.name}-#{spec.version} --no-ri --no-rdoc"
64
+ command = "sudo #{command}" if ENV["SUDO"] == true
65
+ sh %{#{command}}
66
+ end
67
+
68
+ desc "Generate the rdoc"
69
+ Rake::RDocTask.new do |rdoc|
70
+ files = ["README.markdown", "lib/**/*.rb"]
71
+ rdoc.rdoc_files.add(files)
72
+ rdoc.main = "README.markdown"
73
+ rdoc.title = spec.summary
74
+ end
75
+
76
+ task :yank do
77
+ `gem yank #{spec.name} -v #{spec.version}`
78
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ File.dirname(__FILE__) + "/rails/init.rb"
data/lib/cockpit.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_record'
4
+
5
+ this = File.dirname(__FILE__)
6
+ Dir["#{this}/cockpit/*"].each { |c| require c }
7
+
8
+ class Settings
9
+ include Cockpit::Configuration
10
+ end
11
+
12
+ def Settings(*args, &block)
13
+ Settings.configure(*args, &block)
14
+ end
15
+
16
+ def Cockpit(*args, &block)
17
+ Settings(*args, &block)
18
+ end
19
+
20
+ ActiveRecord::Base.send(:include, Cockpit) if defined?(ActiveRecord::Base)
21
+
22
+ require "#{this}/../app/models/setting.rb"
@@ -0,0 +1,94 @@
1
+ module Cockpit
2
+
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def acts_as_configurable(*args, &block)
9
+ options = args.extract_options!
10
+ settings_name = (args.shift || "settings").to_s
11
+ clazz_name = self.to_s.downcase.split("::").last
12
+
13
+ class_inheritable_accessor settings_name
14
+ has_many settings_name, :class_name => "Setting", :as => :configurable
15
+
16
+ Settings { send(clazz_name, &block) }
17
+
18
+ self.send("#{settings_name}=", ::Settings.for(clazz_name))
19
+
20
+ define_method settings_name do |*value|
21
+ unless @settings
22
+ @settings = self.class.send(settings_name).dup
23
+ @settings.configurable = self
24
+ end
25
+
26
+ unless value.empty?
27
+ @settings[value.first]
28
+ else
29
+ @settings
30
+ end
31
+ # model-dependent settings.
32
+ # requires refactoring the Settings module
33
+ # so none of it uses class methods...
34
+ end
35
+
36
+ end
37
+
38
+ def acts_as_settable
39
+ belongs_to :configurable, :polymorphic => true
40
+ end
41
+ end
42
+
43
+ def self.get_type(object)
44
+ result = case object
45
+ when Fixnum
46
+ :integer
47
+ when Array
48
+ :array
49
+ when Float # decimal
50
+ :float
51
+ when String
52
+ :string
53
+ when Proc
54
+ :proc
55
+ when TrueClass
56
+ :boolean
57
+ when FalseClass
58
+ :boolean
59
+ when DateTime
60
+ :datetime
61
+ when Time
62
+ :time
63
+ else
64
+ :string
65
+ end
66
+ end
67
+
68
+ def self.type_cast(object, type)
69
+ return object if type.nil?
70
+ result = case type.to_sym
71
+ when :integer
72
+ object.to_i
73
+ when :array
74
+ object.to_a
75
+ when :float, :decimal # decimal
76
+ object.to_f
77
+ when :string, :text
78
+ object.to_s
79
+ when :boolean
80
+ if object == "true" || object == true || object == "1" || object == 1 || object == "t"
81
+ true
82
+ else
83
+ false
84
+ end
85
+ when :datetime, :timestamp
86
+ object.is_a?(String) ? DateTime.parse(object) : object
87
+ when :time
88
+ object.is_a?(String) ? Time.parse(object) : object
89
+ else
90
+ object.to_s
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,218 @@
1
+ module Cockpit
2
+ module Configuration
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ base.class_eval do
7
+ include InstanceMethods
8
+ end
9
+ end
10
+
11
+ module InstanceMethods
12
+
13
+ attr_accessor :setting_class, :configurable
14
+
15
+ def setting_class
16
+ @setting_class ||= ::Setting
17
+ end
18
+
19
+ def initialize(*args, &block)
20
+ store.tree = args.extract_options!
21
+ value = args
22
+ build(&block)
23
+ end
24
+
25
+ def build(&block)
26
+ tree.instance_eval(&block) if block_given?
27
+ end
28
+
29
+ def configurable=(object)
30
+ @configurable = object
31
+ store.configurable = object
32
+ end
33
+
34
+ # something like this
35
+ # Settings.for(@user.id).set(:allow_email => params[:allow_email])
36
+ # or
37
+ # @user.settings(:allow_email => params[:allow_email])
38
+ def for(configurable_id)
39
+ self
40
+ end
41
+
42
+ def each_setting(&block)
43
+ tree.each_setting(&block)
44
+ end
45
+
46
+ def tree
47
+ store.tree
48
+ end
49
+
50
+ def defaults
51
+ store.defaults
52
+ end
53
+
54
+ def store
55
+ self.store = :memory if @store.nil?
56
+ @store
57
+ end
58
+
59
+ def empty?
60
+ tree.empty?
61
+ end
62
+
63
+ def store=(value)
64
+ options = {:configurable => configurable}
65
+ options[:tree] = @store.tree unless @store.nil?
66
+ @store = case value
67
+ when :memory
68
+ Cockpit::Store::Memory.new(self, options)
69
+ when :db
70
+ Cockpit::Store::Database.new(self, options)
71
+ else
72
+ value
73
+ end
74
+ end
75
+
76
+ def get(path)
77
+ store.get(path)
78
+ end
79
+ alias_method :[], :get
80
+
81
+ def get!(path)
82
+ store.get!(path)
83
+ end
84
+
85
+ def set(value)
86
+ store.set(value)
87
+ end
88
+
89
+ def set!(value)
90
+ store.set!(value)
91
+ end
92
+
93
+ def []=(key, value)
94
+ store.set(key => value)
95
+ end
96
+
97
+ def clear(options = {})
98
+ store.clear(options)
99
+ end
100
+
101
+ def inspect
102
+ "<##{self.class.to_s} @tree=#{tree.inspect}/>"
103
+ end
104
+
105
+ def to_yaml
106
+ to_hash.to_yaml
107
+ end
108
+
109
+ def to_hash
110
+ store.tree.to_hash
111
+ end
112
+
113
+ def method_missing(meth, *args, &block)
114
+ if args.empty?
115
+ store.get(meth)
116
+ else
117
+ store.set(meth, args.first)
118
+ end
119
+ end
120
+
121
+ end
122
+
123
+ module ClassMethods
124
+
125
+ def setting_class
126
+ global.setting_class
127
+ end
128
+
129
+ def global(&block)
130
+ @global ||= new
131
+ @global.build(&block)
132
+ @global
133
+ end
134
+
135
+ def configure(*args, &block)
136
+ options = args.extract_options!
137
+ path = args.first
138
+ if path && !path.is_a?(Hash)
139
+ global[path]
140
+ elsif !options.empty?
141
+ global.set(options)
142
+ else
143
+ global(&block)
144
+ end
145
+ end
146
+
147
+ # something like this
148
+ # Settings.for(@user.id).set(:allow_email => params[:allow_email])
149
+ # or
150
+ # @user.settings(:allow_email => params[:allow_email])
151
+ def for(name)
152
+ Settings.new(get(name).dup)
153
+ end
154
+
155
+ def tree
156
+ global.tree
157
+ end
158
+
159
+ def defaults
160
+ global.defaults
161
+ end
162
+
163
+ def store
164
+ global.store
165
+ end
166
+
167
+ def store=(value)
168
+ global.store = value
169
+ end
170
+
171
+ def get(path)
172
+ global.get(path)
173
+ end
174
+ alias_method :[], :get
175
+
176
+ def get!(path)
177
+ global.get!(path)
178
+ end
179
+
180
+ def set(value)
181
+ global.set(value)
182
+ end
183
+
184
+ def set!(value)
185
+ global.set!(value)
186
+ end
187
+
188
+ def []=(key, value)
189
+ global.set(key => value)
190
+ end
191
+
192
+ def clear(options = {})
193
+ global.clear(options)
194
+ end
195
+
196
+ def empty?
197
+ global.empty?
198
+ end
199
+
200
+ def inspect
201
+ global.inspect
202
+ end
203
+
204
+ def to_yaml
205
+ global.to_yaml
206
+ end
207
+
208
+ def to_hash
209
+ global.to_hash
210
+ end
211
+
212
+ def method_missing(meth, *args, &block)
213
+ global.method_missing(meth, *args, &block)
214
+ end
215
+
216
+ end
217
+ end
218
+ end