kvc 0.0.1

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