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 +20 -0
- data/README.markdown +175 -0
- data/Rakefile +78 -0
- data/init.rb +1 -0
- data/lib/cockpit.rb +22 -0
- data/lib/cockpit/cockpit.rb +94 -0
- data/lib/cockpit/configuration.rb +218 -0
- data/lib/cockpit/definition.rb +55 -0
- data/lib/cockpit/extensions.rb +25 -0
- data/lib/cockpit/helper.rb +44 -0
- data/lib/cockpit/store.rb +103 -0
- data/lib/cockpit/tree_hash.rb +113 -0
- data/rails/init.rb +1 -0
- data/test/lib/database.rb +22 -0
- data/test/lib/user.rb +8 -0
- data/test/test_helper.rb +90 -0
- data/test/test_settings.rb +228 -0
- data/test/test_settings_in_database.rb +153 -0
- data/test/test_settings_on_model.rb +68 -0
- metadata +86 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module Cockpit
|
2
|
+
# Represents the definition of a preference for a particular model
|
3
|
+
class Definition
|
4
|
+
# The data type for the content stored in this preference type
|
5
|
+
attr_reader :type
|
6
|
+
|
7
|
+
def initialize(name, *args) #:nodoc:
|
8
|
+
options = args.extract_options!
|
9
|
+
|
10
|
+
@type = args.first ? args.first.to_sym : :boolean
|
11
|
+
|
12
|
+
# Create a column that will be responsible for typecasting
|
13
|
+
@column = ActiveRecord::ConnectionAdapters::Column.new(name.to_s, options[:default], @type == :any ? nil : @type.to_s)
|
14
|
+
|
15
|
+
@group_defaults = (options[:group_defaults] || {}).inject({}) do |defaults, (group, default)|
|
16
|
+
defaults[group.is_a?(Symbol) ? group.to_s : group] = type_cast(default)
|
17
|
+
defaults
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# The name of the preference
|
22
|
+
def name
|
23
|
+
@column.name
|
24
|
+
end
|
25
|
+
|
26
|
+
# The default value to use for the preference in case none have been
|
27
|
+
# previously defined
|
28
|
+
def default_value(group = nil)
|
29
|
+
@group_defaults.include?(group) ? @group_defaults[group] : @column.default
|
30
|
+
end
|
31
|
+
|
32
|
+
# Determines whether column backing this preference stores numberic values
|
33
|
+
def number?
|
34
|
+
@column.number?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Typecasts the value based on the type of preference that was defined.
|
38
|
+
# This uses ActiveRecord's typecast functionality so the same rules for
|
39
|
+
# typecasting a model's columns apply here.
|
40
|
+
def type_cast(value)
|
41
|
+
@type == :any ? value : @column.type_cast(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Typecasts the value to true/false depending on the type of preference
|
45
|
+
def query(value)
|
46
|
+
if !(value = type_cast(value))
|
47
|
+
false
|
48
|
+
elsif number?
|
49
|
+
!value.zero?
|
50
|
+
else
|
51
|
+
!value.blank?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Hash
|
2
|
+
def recursively_symbolize_keys!
|
3
|
+
self.symbolize_keys!
|
4
|
+
self.values.each do |v|
|
5
|
+
if v.is_a? Hash
|
6
|
+
v.recursively_symbolize_keys!
|
7
|
+
elsif v.is_a? Array
|
8
|
+
v.recursively_symbolize_keys!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Array
|
16
|
+
def recursively_symbolize_keys!
|
17
|
+
self.each do |item|
|
18
|
+
if item.is_a? Hash
|
19
|
+
item.recursively_symbolize_keys!
|
20
|
+
elsif item.is_a? Array
|
21
|
+
item.recursively_symbolize_keys!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Cockpit::Helper
|
2
|
+
|
3
|
+
# always returns either an array or a string
|
4
|
+
def c(*args)
|
5
|
+
options = args.extract_options!
|
6
|
+
result = args.collect {|i| Settings.get(i).value }
|
7
|
+
result = result.pop if result.length == 1
|
8
|
+
result.blank? ? nil : result.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def setting_tag(tag)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def settings_tag(key, &block)
|
16
|
+
Settings(key).each_setting do |key, attributes, value|
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def setting_value(value)
|
22
|
+
result = case value
|
23
|
+
when Proc
|
24
|
+
value.call
|
25
|
+
when Cockpit::TreeHash
|
26
|
+
value
|
27
|
+
else
|
28
|
+
value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def setting_options(attributes)
|
33
|
+
return {} unless (attributes.is_a?(Hash) && attributes[:options])
|
34
|
+
options = case attributes[:options]
|
35
|
+
when Proc
|
36
|
+
attributes[:options].call
|
37
|
+
else
|
38
|
+
attributes[:options]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
ActionView::Base.send(:include, Cockpit::Helper) if defined?(ActionView)
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Cockpit
|
2
|
+
module Store
|
3
|
+
class Base
|
4
|
+
attr_accessor :configurable
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
options = args.extract_options!
|
8
|
+
options.each do |k, v|
|
9
|
+
self.send("#{k.to_s}=", v) if self.respond_to?("#{k.to_s}=")
|
10
|
+
end
|
11
|
+
@configuration = args.first
|
12
|
+
end
|
13
|
+
|
14
|
+
def configuration
|
15
|
+
@configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
def tree=(value)
|
19
|
+
@tree = value if value.is_a?(TreeHash)
|
20
|
+
@tree
|
21
|
+
end
|
22
|
+
|
23
|
+
def tree
|
24
|
+
@tree ||= TreeHash.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear(options = {})
|
28
|
+
tree.each do |k, v|
|
29
|
+
tree.delete(k) unless (options[:except] && options[:except].include?(k.to_sym))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(path)
|
34
|
+
tree.get(path)
|
35
|
+
end
|
36
|
+
alias_method :[], :get
|
37
|
+
|
38
|
+
def get!(path)
|
39
|
+
result = get(path)
|
40
|
+
raise "'#{path.to_s}' was not in Config" if result.blank?
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(value)
|
45
|
+
return unless value.is_a?(Hash)
|
46
|
+
value.each { |k,v| set_one(k, v) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_one(key, value)
|
50
|
+
tree.set(key, value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def set!(value)
|
54
|
+
result = set(value)
|
55
|
+
raise "'#{path.to_s}' was not set in Config" if result.blank?
|
56
|
+
result
|
57
|
+
end
|
58
|
+
|
59
|
+
def []=(key, value)
|
60
|
+
set_one(key => value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Memory < Base
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
class Database < Base
|
69
|
+
|
70
|
+
def clear(options = {})
|
71
|
+
configuration.setting_class.all.collect(&:destroy) if options[:hard] == true
|
72
|
+
super(options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_or_create(key)
|
76
|
+
result = configuration.setting_class.find(key.to_s) rescue nil
|
77
|
+
result ||= configuration.setting_class.create(:key => key.to_s)
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_one_with_database(key, value)
|
81
|
+
setting = find_or_create(key)
|
82
|
+
cast_as = (setting.cast_as || Cockpit.get_type(value)).to_s
|
83
|
+
attributes = {:value => value, :cast_as => cast_as}
|
84
|
+
attributes[:configurable] = self.configurable if self.configurable
|
85
|
+
setting.update_attributes(attributes)
|
86
|
+
set_one_without_database(key, Cockpit.type_cast(value, cast_as))
|
87
|
+
end
|
88
|
+
alias_method_chain :set_one, :database
|
89
|
+
|
90
|
+
def get(key)
|
91
|
+
result = super(key)
|
92
|
+
if result.blank? && setting = find_or_create(key)
|
93
|
+
set(key => setting.value)
|
94
|
+
result = super(key)
|
95
|
+
else
|
96
|
+
|
97
|
+
end
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# http://www.daniel-azuma.com/blog/view/z3bqa0t01uugg1/implementing_dsl_blocks
|
2
|
+
module Cockpit
|
3
|
+
class TreeHash < Hash
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
block = Proc.new {|h,k| h[k] = TreeHash.new(&block)}
|
7
|
+
super &block
|
8
|
+
end
|
9
|
+
|
10
|
+
def value_type
|
11
|
+
has_key?(:type) ? self[:type] : :string
|
12
|
+
end
|
13
|
+
|
14
|
+
def each_setting(&block)
|
15
|
+
dup.each do |k, v|
|
16
|
+
atrib = Hash.new
|
17
|
+
if v.has_key?(:value)
|
18
|
+
value = clone(v[:value])
|
19
|
+
atrib = clone(v)
|
20
|
+
else
|
21
|
+
v.each do |k, sub_v|
|
22
|
+
atrib[k] = clone(v[k]) unless sub_v.is_a?(TreeHash)
|
23
|
+
end
|
24
|
+
value = clone(v)
|
25
|
+
end
|
26
|
+
yield(k.to_s, atrib, value) if block_given?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def dup
|
31
|
+
result = super
|
32
|
+
result.each do |k,v|
|
33
|
+
result[k] = v.dup if v.is_a?(TreeHash)
|
34
|
+
end
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def children
|
39
|
+
hash = {}
|
40
|
+
each do |k, v|
|
41
|
+
hash[k] = v if v.is_a?(Hash)
|
42
|
+
end
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(key)
|
47
|
+
traverse(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_attribute(key, attribute)
|
51
|
+
get(key)[attribute.to_sym]
|
52
|
+
end
|
53
|
+
|
54
|
+
def set(key, value)
|
55
|
+
traverse(key, value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_attribute(key, value)
|
59
|
+
traverse(key, value, false)
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_attributes(hash)
|
63
|
+
hash.each { |k, v| set_attribute(k, v) }
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_hash
|
68
|
+
hash = {}
|
69
|
+
self.each do |k, v|
|
70
|
+
hash[k] = v.is_a?(TreeHash) ? v.to_hash : v
|
71
|
+
end
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
def traverse(path, value = nil, as_node = true)
|
77
|
+
path = path.to_s.split('.')
|
78
|
+
child = path.pop.to_sym
|
79
|
+
parent = path.inject(self) { |h,k| h[k.to_sym] }
|
80
|
+
unless value.nil?
|
81
|
+
if as_node
|
82
|
+
if parent[child].has_key?(:type)
|
83
|
+
parent[child][:value] = Cockpit.type_cast(value, parent[child][:type])
|
84
|
+
else
|
85
|
+
parent[child][:value] = value
|
86
|
+
parent[child][:type] = Cockpit.get_type(value)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
parent[child] = value
|
90
|
+
end
|
91
|
+
end
|
92
|
+
parent[child]
|
93
|
+
end
|
94
|
+
|
95
|
+
def method_missing(meth, *args, &block)
|
96
|
+
options = args.extract_options!
|
97
|
+
meth = meth.to_s.gsub("=", "").to_sym
|
98
|
+
if args.empty?
|
99
|
+
return self[meth] if self.has_key?(meth)
|
100
|
+
found = get(meth).set_attributes(options)
|
101
|
+
found = found.instance_eval(&block) if block_given?
|
102
|
+
found
|
103
|
+
else
|
104
|
+
get(meth).set_attributes({:type => Cockpit.get_type(args.first)}.merge(options).merge(:value => args.first))
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def clone(object)
|
110
|
+
(object.respond_to?(:dup) ? object.dup : object) rescue object
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'cockpit'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
begin
|
2
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
3
|
+
rescue ArgumentError
|
4
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
5
|
+
end
|
6
|
+
|
7
|
+
ActiveRecord::Base.configurations = true
|
8
|
+
|
9
|
+
ActiveRecord::Schema.define(:version => 1) do
|
10
|
+
|
11
|
+
create_table :settings, :force => true do |t|
|
12
|
+
t.string :key
|
13
|
+
t.string :value
|
14
|
+
t.string :cast_as
|
15
|
+
t.string :configurable_type
|
16
|
+
t.integer :configurable_id
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table "users", :force => true do |t|
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/test/lib/user.rb
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "ruby-debug"
|
3
|
+
gem 'test-unit'
|
4
|
+
require "test/unit"
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/test_case'
|
7
|
+
require 'active_record'
|
8
|
+
require 'active_record/fixtures'
|
9
|
+
require 'shoulda'
|
10
|
+
require 'shoulda/active_record'
|
11
|
+
|
12
|
+
require File.dirname(__FILE__) + '/lib/database'
|
13
|
+
|
14
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '/../lib/cockpit'))
|
15
|
+
|
16
|
+
require File.dirname(__FILE__) + '/lib/user'
|
17
|
+
|
18
|
+
ActiveRecord::Base.class_eval do
|
19
|
+
def self.detonate
|
20
|
+
all.map(&:destroy)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveSupport::TestCase.class_eval do
|
25
|
+
|
26
|
+
def load_settings
|
27
|
+
Settings do
|
28
|
+
asset :title => "Asset (and related) Settings" do
|
29
|
+
thumb do
|
30
|
+
width 100, :tip => "Thumb's width"
|
31
|
+
height 100, :tip => "Thumb's height"
|
32
|
+
end
|
33
|
+
medium do
|
34
|
+
width 600, :tip => "Thumb's width"
|
35
|
+
height 250, :tip => "Thumb's height"
|
36
|
+
end
|
37
|
+
large do
|
38
|
+
width 600, :tip => "Large's width"
|
39
|
+
height 295, :tip => "Large's height"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
authentication :title => "Authentication Settings" do
|
43
|
+
use_open_id true
|
44
|
+
use_oauth true
|
45
|
+
end
|
46
|
+
front_page do
|
47
|
+
slideshow_tag "slideshow"
|
48
|
+
slideshow_effect "fade"
|
49
|
+
end
|
50
|
+
page do
|
51
|
+
per_page 10
|
52
|
+
feed_per_page 10
|
53
|
+
end
|
54
|
+
people do
|
55
|
+
show_avatars true
|
56
|
+
default_avatar "/images/missing-person.png"
|
57
|
+
end
|
58
|
+
site do
|
59
|
+
title "Martini"
|
60
|
+
tagline "Developer Friendly, Client Ready Blog with Rails 3"
|
61
|
+
keywords "Rails 3, Heroku, JQuery, HTML 5, Blog Engine, CSS3"
|
62
|
+
copyright "© 2010 Viatropos. All rights reserved."
|
63
|
+
timezones :value => lambda { TimeZone.first }, :options => lambda { TimeZone.all }
|
64
|
+
date_format "%m %d, %Y"
|
65
|
+
time_format "%H"
|
66
|
+
week_starts_on "Monday", :options => ["Monday", "Sunday", "Friday"]
|
67
|
+
language "en-US", :options => ["en-US", "de"]
|
68
|
+
touch_enabled true
|
69
|
+
touch_as_subdomain false
|
70
|
+
google_analytics ""
|
71
|
+
teasers :title => "Teasers" do
|
72
|
+
disable false
|
73
|
+
left 1, :title => "Left Teaser"
|
74
|
+
right 2
|
75
|
+
center 3
|
76
|
+
end
|
77
|
+
main_quote 1
|
78
|
+
end
|
79
|
+
social do
|
80
|
+
facebook "http://facebook.com/viatropos"
|
81
|
+
twitter "http://twitter.com/viatropos"
|
82
|
+
email "lancejpollard@gmail.com"
|
83
|
+
end
|
84
|
+
s3 do
|
85
|
+
key "my_key"
|
86
|
+
secret "my_secret"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|