kvc 0.0.1
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/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
|
+
|