cockpit 0.0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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