kvc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/Manifest.txt +10 -0
- data/README.txt +117 -0
- data/Rakefile +34 -0
- data/app/models/kvc/settings.rb +76 -0
- data/lib/kvc.rb +65 -0
- data/lib/kvc/settings_proxy.rb +27 -0
- data/tasks/kvc_tasks.rake +8 -0
- data/test/kvc_test.rb +130 -0
- data/uninstall.rb +9 -0
- metadata +75 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
= KVC
|
2
|
+
|
3
|
+
http://github.com/stephencelis/kvc
|
4
|
+
|
5
|
+
|
6
|
+
== DESCRIPTION
|
7
|
+
|
8
|
+
KVC (Key-Value Configuration) provides a powerful, transparent way to maintain
|
9
|
+
mutable app settings in the database.
|
10
|
+
|
11
|
+
|
12
|
+
== FEATURES/PROBLEMS
|
13
|
+
|
14
|
+
* Transparent: no generators or migrations to run.
|
15
|
+
* Powerful: quick access; automatic updates; scales quickly.
|
16
|
+
|
17
|
+
For more rigidity/safety: http://github.com/stephencelis/acts_as_singleton
|
18
|
+
|
19
|
+
|
20
|
+
== SYNOPSIS
|
21
|
+
|
22
|
+
Need a mutable setting in the database? Just add it, KVC-style:
|
23
|
+
|
24
|
+
KVC.key = "value"
|
25
|
+
|
26
|
+
|
27
|
+
You're set.
|
28
|
+
|
29
|
+
Any value will do:
|
30
|
+
|
31
|
+
KVC.last_import = Time.zone.now
|
32
|
+
|
33
|
+
|
34
|
+
However you do:
|
35
|
+
|
36
|
+
KVC.site_messages.unshift("Downtime expected at 0800.").pop
|
37
|
+
|
38
|
+
|
39
|
+
Store related settings together in a hash to reduce queries:
|
40
|
+
|
41
|
+
KVC.homepage_settings = { ... }.with_indifferent_access
|
42
|
+
KVC.homepage_settings # => { ... }
|
43
|
+
|
44
|
+
|
45
|
+
If need be, validate specific key values in an initializer:
|
46
|
+
|
47
|
+
# config/initializers/kvc_config.rb
|
48
|
+
KVC::Settings.config do
|
49
|
+
validates("password") { |value| value =~ /\d+/ }
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
== REQUIREMENTS
|
54
|
+
|
55
|
+
* Rails 2.3.2 or greater.
|
56
|
+
|
57
|
+
|
58
|
+
== INSTALL
|
59
|
+
|
60
|
+
=== As a gem
|
61
|
+
|
62
|
+
Install:
|
63
|
+
|
64
|
+
% gem install stephencelis-kvc --source=http://gems.github.com
|
65
|
+
|
66
|
+
|
67
|
+
And configure:
|
68
|
+
|
69
|
+
config.gem "stephencelis-kvc",
|
70
|
+
:lib => "kvc",
|
71
|
+
:source => "http://gems.github.com"
|
72
|
+
|
73
|
+
|
74
|
+
=== As a plugin
|
75
|
+
|
76
|
+
Install:
|
77
|
+
|
78
|
+
% script/plugin install git://github.com/stephencelis/kvc.git
|
79
|
+
|
80
|
+
|
81
|
+
Or submodule:
|
82
|
+
|
83
|
+
% git submodule add git://github.com/stephencelis/kvc.git vendor/plugins/kvc
|
84
|
+
|
85
|
+
|
86
|
+
=== Anything else?
|
87
|
+
|
88
|
+
Just restart your server, and you should be good to go. The key-value table
|
89
|
+
will migrate transparently when the model first loads.
|
90
|
+
|
91
|
+
If you uninstall the plugin, you will be prompted to drop the table (to drop
|
92
|
+
the table at any time, execute the "kvc:drop_table" Rake task).
|
93
|
+
|
94
|
+
|
95
|
+
== LICENSE
|
96
|
+
|
97
|
+
(The MIT License)
|
98
|
+
|
99
|
+
(c) 2009-* Stephen Celis, stephen@stephencelis.com.
|
100
|
+
|
101
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
102
|
+
of this software and associated documentation files (the "Software"), to deal
|
103
|
+
in the Software without restriction, including without limitation the rights
|
104
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
105
|
+
copies of the Software, and to permit persons to whom the Software is
|
106
|
+
furnished to do so, subject to the following conditions:
|
107
|
+
|
108
|
+
The above copyright notice and this permission notice shall be included in all
|
109
|
+
copies or substantial portions of the Software.
|
110
|
+
|
111
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
112
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
113
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
114
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
115
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
116
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
117
|
+
SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + "/lib"
|
2
|
+
require "rubygems"
|
3
|
+
require "hoe"
|
4
|
+
require "active_record"
|
5
|
+
require "kvc"
|
6
|
+
|
7
|
+
Hoe.new("kvc", KVC::VERSION) do |p|
|
8
|
+
p.developer("Stephen Celis", "stephen@stephencelis.com")
|
9
|
+
p.remote_rdoc_dir = ''
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'rake'
|
13
|
+
require 'rake/testtask'
|
14
|
+
require 'rake/rdoctask'
|
15
|
+
|
16
|
+
desc 'Default: run unit tests.'
|
17
|
+
task :default => :test
|
18
|
+
|
19
|
+
desc 'Test the KVC plugin.'
|
20
|
+
Rake::TestTask.new(:test) do |t|
|
21
|
+
t.libs << 'lib'
|
22
|
+
t.libs << 'test'
|
23
|
+
t.pattern = 'test/**/*_test.rb'
|
24
|
+
t.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Generate documentation for the kvc plugin.'
|
28
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
29
|
+
rdoc.rdoc_dir = 'rdoc'
|
30
|
+
rdoc.title = 'KVC'
|
31
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
32
|
+
rdoc.rdoc_files.include('README')
|
33
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
34
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# KVC::Settings is a model rarely accessed like other Active Record models.
|
2
|
+
# You can still fetch records from the database as in other models, but you
|
3
|
+
# will more likely read and write records through the KVC namespace directly:
|
4
|
+
#
|
5
|
+
# KVC.key = "value"
|
6
|
+
# # Creates #<KVC::Settings id: 1, key: "key", value: "---\n\"value\"">
|
7
|
+
# # Values are serialized to accommodate complex object storage.
|
8
|
+
#
|
9
|
+
# KVC.key # => "value"
|
10
|
+
# # Fetches #<KVC::Settings id: 1, key: "key", value: "---\n\"value\"">
|
11
|
+
class KVC::Settings < ActiveRecord::Base
|
12
|
+
# Do not raise exceptions on keys that don't exist.
|
13
|
+
@@strict_keys = false
|
14
|
+
cattr_accessor :strict_keys
|
15
|
+
|
16
|
+
@@validations = HashWithIndifferentAccess.new []
|
17
|
+
cattr_reader :validations
|
18
|
+
class << self
|
19
|
+
alias strict_keys? strict_keys
|
20
|
+
|
21
|
+
# Config takes a block for configuration. For now, this provides
|
22
|
+
# rudimentary validation. E.g.:
|
23
|
+
#
|
24
|
+
# KVC::Settings.config do
|
25
|
+
# validates("username") { |value| value.is_a? String }
|
26
|
+
# validates("username") { |value| (2..16).include? value.to_s.length }
|
27
|
+
# end
|
28
|
+
def config(&block)
|
29
|
+
Object.new.instance_eval do
|
30
|
+
def validates(*args, &proc)
|
31
|
+
@@validations[args] << proc
|
32
|
+
end
|
33
|
+
self
|
34
|
+
end.instance_eval(&block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Active Record does not automatically namespace tables.
|
39
|
+
set_table_name :kvc_settings
|
40
|
+
|
41
|
+
# Validate in case the unique index isn't enough.
|
42
|
+
validates_uniqueness_of :key
|
43
|
+
|
44
|
+
# Deserializes value from database.
|
45
|
+
def value
|
46
|
+
@value ||= YAML.load(read_attribute(:value))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Serializes value for database.
|
50
|
+
def value=(input)
|
51
|
+
returning @value = input do
|
52
|
+
write_attribute :value, input.to_yaml
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def validate
|
59
|
+
unless @@validations[key].map { |validation| validation.call(value) }.all?
|
60
|
+
errors.add_to_base "#{key} cannot be set to #{value}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if !table_exists?
|
65
|
+
ActiveRecord::Schema.define do
|
66
|
+
create_table :kvc_settings do |t|
|
67
|
+
t.string :key
|
68
|
+
t.text :value
|
69
|
+
|
70
|
+
t.timestamps
|
71
|
+
end
|
72
|
+
|
73
|
+
add_index :kvc_settings, :key, :unique => true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/kvc.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# KVC (Key-Value Configuration) provides a powerful, transparent way to
|
2
|
+
# maintain mutable app settings in the database.
|
3
|
+
#
|
4
|
+
# KVC.key = "value" # Saves key-value pairing to a database record.
|
5
|
+
# KVC.key # Retrieves the record and returns the "value".
|
6
|
+
#
|
7
|
+
# Use the index syntax to avoid conflicts with Module methods:
|
8
|
+
#
|
9
|
+
# KVC.name = "Ruby Inside"
|
10
|
+
# KVC.name # => "KVC" (Whoops.)
|
11
|
+
# KVC["name"] # => "Ruby Inside"
|
12
|
+
#
|
13
|
+
# Or set boolean methods (values are serialized, so booleans are fair game):
|
14
|
+
#
|
15
|
+
# KVC["display_warning?"] = true
|
16
|
+
# KVC.display_warning? # => true
|
17
|
+
#
|
18
|
+
# Or get creative:
|
19
|
+
#
|
20
|
+
# KVC["Chad's expensive guitar"] = Fender::Stratocaster.new(1957)
|
21
|
+
#
|
22
|
+
# By default, nonexistent keys return +nil+ values, but you can be stricter if
|
23
|
+
# you want to avoid typo-based bugs:
|
24
|
+
#
|
25
|
+
# KVC.a_typo_could_occur? # => nil
|
26
|
+
# KVC::Settings.strict_keys = true #
|
27
|
+
# KVC.a_typo_could_occur? # Raises NoMethodError.
|
28
|
+
#
|
29
|
+
# If you really need validations, define them in an initializer. E.g.:
|
30
|
+
#
|
31
|
+
# # config/initializers/kvc_config.rb
|
32
|
+
# KVC::Settings.config do
|
33
|
+
# validates("password") { |value| value =~ /\d+/ }
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Failed validations will raise <tt>ActiveRecord::RecordInvalid</tt>.
|
37
|
+
module KVC
|
38
|
+
VERSION = "0.0.1"
|
39
|
+
|
40
|
+
class << self
|
41
|
+
private
|
42
|
+
|
43
|
+
# Handles the key-value magic.
|
44
|
+
def method_missing(method, *args, &block)
|
45
|
+
key = method.to_s
|
46
|
+
key.sub!(/^\[\](=?)$/) { "#{args.shift}#{$1}" }
|
47
|
+
|
48
|
+
if key.sub!(/=$/) {} # Is it a writer method?
|
49
|
+
returning args.shift do |value|
|
50
|
+
setting = KVC::Settings.find_or_initialize_by_key(key)
|
51
|
+
setting.update_attributes!(:value => value)
|
52
|
+
end
|
53
|
+
elsif setting = KVC::Settings.find_by_key(key) # Is it a reader?
|
54
|
+
if args.present?
|
55
|
+
error_message = "wrong number of arguments (#{args.length} for 0)"
|
56
|
+
raise ArgumentError, error_message
|
57
|
+
end
|
58
|
+
|
59
|
+
KVC::SettingsProxy.new(setting)
|
60
|
+
elsif args.present? || KVC::Settings.strict_keys?
|
61
|
+
raise NoMethodError
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# The KVC::SettingsProxy proxies to a KVC::Settings object's value. The proxy
|
2
|
+
# ensures that when a value is modified, the Settings object is immediately
|
3
|
+
# updated in the database.
|
4
|
+
#
|
5
|
+
# These proxies also provide access to the Settings object's timestamp
|
6
|
+
# attributes.
|
7
|
+
class KVC::SettingsProxy
|
8
|
+
undef_method *instance_methods.grep(/^(?!__|nil\?|send)/)
|
9
|
+
|
10
|
+
def initialize(setting)
|
11
|
+
@setting = setting
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def method_missing(method, *args, &block)
|
17
|
+
return @setting.send(method) if [:created_at, :updated_at].include? method
|
18
|
+
|
19
|
+
return_value = @setting.value.send(method, *args, &block)
|
20
|
+
if @setting.value != YAML.load(@setting.read_attribute(:value))
|
21
|
+
@setting.update_attribute :value, @setting.value
|
22
|
+
return self # Allow for chaining.
|
23
|
+
end
|
24
|
+
|
25
|
+
return_value
|
26
|
+
end
|
27
|
+
end
|
data/test/kvc_test.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_record'
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/test_case'
|
6
|
+
|
7
|
+
$: << File.expand_path(File.dirname(__FILE__) + "/../lib") <<
|
8
|
+
File.expand_path(File.dirname(__FILE__) + "/../app/models")
|
9
|
+
|
10
|
+
ActiveRecord::Base.establish_connection :adapter => 'sqlite3',
|
11
|
+
:dbfile => ':memory:'
|
12
|
+
|
13
|
+
require "kvc"
|
14
|
+
require "kvc/settings"
|
15
|
+
require "kvc/settings_proxy"
|
16
|
+
|
17
|
+
logger = Logger.new(STDOUT)
|
18
|
+
|
19
|
+
class KVCTest < ActiveSupport::TestCase
|
20
|
+
teardown do
|
21
|
+
KVC::Settings.destroy_all
|
22
|
+
end
|
23
|
+
|
24
|
+
test "KVC::Settings table should exist" do
|
25
|
+
assert KVC::Settings.table_exists?
|
26
|
+
end
|
27
|
+
|
28
|
+
test "method_missing should re-raise if there are arguments" do
|
29
|
+
assert_raise NoMethodError do
|
30
|
+
KVC.this_should("not_work")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
test "fake attributes should return nil if we're not being strict" do
|
35
|
+
assert_nil KVC.nonexistent_key
|
36
|
+
end
|
37
|
+
|
38
|
+
test "fake attributes should raise exception if we're being strict" do
|
39
|
+
begin
|
40
|
+
assert_equal false, KVC::Settings.strict_keys? # Avoid nil-check.
|
41
|
+
KVC::Settings.strict_keys = true
|
42
|
+
assert KVC::Settings.strict_keys?
|
43
|
+
assert_raise(NoMethodError) { KVC.nonexistent_key }
|
44
|
+
ensure
|
45
|
+
KVC::Settings.strict_keys = false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
test "can set attributes" do
|
50
|
+
assert_difference "KVC::Settings.count" do
|
51
|
+
assert_nil KVC.favorite_food
|
52
|
+
assert_equal "popsicles", KVC.favorite_food = "popsicles"
|
53
|
+
assert_equal "popsicles", KVC.favorite_food
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
test "attributes should update if the changed" do
|
58
|
+
KVC.mutable_array = []
|
59
|
+
KVC.mutable_array << 1
|
60
|
+
assert_equal [1], KVC::Settings.find_by_key("mutable_array").value
|
61
|
+
|
62
|
+
KVC.mutable_string = ""
|
63
|
+
KVC.mutable_string << "change"
|
64
|
+
assert_equal "change", KVC::Settings.find_by_key("mutable_string").value
|
65
|
+
KVC.mutable_string.sub!(/ch/) { "r" }
|
66
|
+
assert_equal "range", KVC::Settings.find_by_key("mutable_string").value
|
67
|
+
end
|
68
|
+
|
69
|
+
test "attribute changes should chain" do
|
70
|
+
KVC.mutable_array = [1, 2, 3]
|
71
|
+
(KVC.mutable_array << 4).shift
|
72
|
+
assert_equal [2, 3, 4], KVC.mutable_array
|
73
|
+
end
|
74
|
+
|
75
|
+
test "attributes should serialize and deserialize" do
|
76
|
+
type = KVC::Settings.columns.find { |column| column.name == "key" }.type
|
77
|
+
assert_equal :string, type
|
78
|
+
|
79
|
+
KVC.favorite_year = 1984
|
80
|
+
assert_kind_of Integer, KVC.favorite_year
|
81
|
+
end
|
82
|
+
|
83
|
+
test "can set attributes with whitespace and symbols" do
|
84
|
+
assert KVC["uses_git?"] = true
|
85
|
+
assert KVC.uses_git?
|
86
|
+
|
87
|
+
assert KVC["Nonstandard, but a fine time :)"] = Time.now
|
88
|
+
assert_instance_of Time, KVC["Nonstandard, but a fine time :)"]
|
89
|
+
end
|
90
|
+
|
91
|
+
test "should have access to created_at and updated_at via proxy" do
|
92
|
+
KVC.apples = [:braeburn, :granny_smith, :fuji, :macintosh]
|
93
|
+
apples = KVC.apples
|
94
|
+
assert_instance_of Time, apples.created_at
|
95
|
+
assert_equal apples.created_at, apples.updated_at
|
96
|
+
end
|
97
|
+
|
98
|
+
test "brackets and equals should not necessarily clash" do
|
99
|
+
KVC["[]=''"] = "upside-down cookie monster"
|
100
|
+
assert_equal "upside-down cookie monster", KVC["[]=''"]
|
101
|
+
end
|
102
|
+
|
103
|
+
test "cannot create duplicate attributes" do
|
104
|
+
KVC.unique = true
|
105
|
+
assert_no_difference "KVC::Settings.count" do
|
106
|
+
KVC::Settings.create :key => "unique", :value => true
|
107
|
+
|
108
|
+
assert_raise ActiveRecord::StatementInvalid do
|
109
|
+
try_again = KVC::Settings.new :key => "unique", :value => true
|
110
|
+
try_again.save_without_validation
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
test "validations should raise exceptions when invalid" do
|
116
|
+
begin
|
117
|
+
KVC::Settings.config do
|
118
|
+
validates("password") { |value| value =~ /\d+/ }
|
119
|
+
end
|
120
|
+
assert_raise ActiveRecord::RecordInvalid do
|
121
|
+
KVC.password = "password"
|
122
|
+
end
|
123
|
+
assert_nothing_raised do
|
124
|
+
KVC.password = "password1"
|
125
|
+
end
|
126
|
+
ensure
|
127
|
+
KVC::Settings.validations["password"] = []
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require Rails.root.join("config", "environment")
|
2
|
+
|
3
|
+
if KVC::Settings.table_exists?
|
4
|
+
print "Also drop the KVC table? [yN] "
|
5
|
+
if STDIN.gets.chomp =~ /^y)/i
|
6
|
+
ActiveRecord::Base.connection.drop_table :kvc_settings
|
7
|
+
puts "Successfully dropped KVC::Settings."
|
8
|
+
end
|
9
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kvc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephen Celis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-03 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.11.0
|
24
|
+
version:
|
25
|
+
description: KVC (Key-Value Configuration) provides a powerful, transparent way to maintain mutable app settings in the database.
|
26
|
+
email:
|
27
|
+
- stephen@stephencelis.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- History.txt
|
34
|
+
- Manifest.txt
|
35
|
+
- README.txt
|
36
|
+
files:
|
37
|
+
- History.txt
|
38
|
+
- Manifest.txt
|
39
|
+
- README.txt
|
40
|
+
- Rakefile
|
41
|
+
- app/models/kvc/settings.rb
|
42
|
+
- lib/kvc.rb
|
43
|
+
- lib/kvc/settings_proxy.rb
|
44
|
+
- tasks/kvc_tasks.rake
|
45
|
+
- test/kvc_test.rb
|
46
|
+
- uninstall.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/stephencelis/kvc
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --main
|
52
|
+
- README.txt
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project: kvc
|
70
|
+
rubygems_version: 1.3.1
|
71
|
+
signing_key:
|
72
|
+
specification_version: 2
|
73
|
+
summary: KVC (Key-Value Configuration) provides a powerful, transparent way to maintain mutable app settings in the database.
|
74
|
+
test_files: []
|
75
|
+
|