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.
@@ -0,0 +1,5 @@
1
+ === 0.0.1 / 2009-04-01
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
@@ -0,0 +1,10 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ app/models/kvc/settings.rb
6
+ lib/kvc.rb
7
+ lib/kvc/settings_proxy.rb
8
+ tasks/kvc_tasks.rake
9
+ test/kvc_test.rb
10
+ uninstall.rb
@@ -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.
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ namespace :kvc do
2
+ desc "Drop the KVC::Settings table"
3
+ task :drop_table do
4
+ if KVC::Settings.table_exists?
5
+ ActiveRecord::Base.connection.drop_table :kvc_settings
6
+ end
7
+ end
8
+ end
@@ -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
@@ -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
+