kb-configurator 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Brennan Dunn
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,114 @@
1
+ = Configurator
2
+
3
+ == Expalanation
4
+
5
+ Unleash your models and quickly and easily annotate anything. Store booleans, strings, or optionally serialized objects (hashes, custom classes, whatever) without extra migrations.
6
+
7
+ Configurator is meant to store basic things. There's no easy way to query for models that match a particular criteria, so don't go overboard. This satisfies some of my needs, but I don't recommend relying on it as a replacement for traditional model fields.
8
+
9
+ Want your users to be able to define custom settings?
10
+
11
+ class User < ActiveRecord::Base
12
+ include Configurator
13
+ end
14
+
15
+ === Basics
16
+
17
+ Now you can do things like:
18
+
19
+ @user.config[:receive_email_alerts?] = true
20
+
21
+ or
22
+
23
+ @user.config[:notification_address] = 'user@gmail.com'
24
+
25
+
26
+ You can set configurations to either instances or classes. Setting key/value pairs to classes is especially useful for setting up application-wide settings.
27
+
28
+ Think of it as just a giant hash.
29
+
30
+ @user.config = { :favorite_animal => 'dog', :favorite_color => 'blue' }
31
+
32
+
33
+ === Namespaces
34
+
35
+ Support for one level of namespacing:
36
+
37
+ @user.config[:animals, :favorite] = 'cat'
38
+
39
+ Namespaces within hash assignments:
40
+
41
+ @user.config = { :animals => { :favorite => 'cat', :likes_elephants? => true }, :artists => { :favorite => 'Radiohead' } }
42
+
43
+ Querying namespaces:
44
+
45
+ @user.config = { :animals => { :cat => 'Toby', :dog => 'Gabby' } }
46
+ @user.config.namespace(:animals) # => { :cat => 'Toby', :dog => 'Gabby' }
47
+
48
+
49
+ === Form support
50
+
51
+ Easy to use in views:
52
+
53
+ <% fields_for :config, @user.config do |c| %>
54
+
55
+ <%= c.select :favorite_color, %w(red green blue) %>
56
+
57
+ <% end %>
58
+
59
+
60
+ === Default Options
61
+
62
+ Databases don't come filled, so there's an easy way to set defaults on your models. The default values will be added to the config table used by the plugin when loaded.
63
+
64
+ class User < ActiveRecord::Base
65
+ include Configurator
66
+
67
+ default_configuration :favorite_color => 'red', :receive_email_alerts? => true, :salary => { :default_for_manager => '$55,000', :default_for_employee => '$25,000' }
68
+ end
69
+
70
+ @user.config[:favorite_color] # => 'red'
71
+ @user.config[:favorite_color] = 'green'
72
+ @user.config[:favorite_color] # => 'green'
73
+
74
+
75
+ == Global Settings
76
+
77
+ Sometimes you don't want to be restricted to configuring records, and would like to apply Configurator to a class or to something even more global. Well, now you can.
78
+
79
+ === Per Model
80
+
81
+ User.config[:notification_email] = 'Welcome new user!'
82
+
83
+ === Globally
84
+
85
+ Configurator[:default_notification_email] = 'Welcome to our website!'
86
+
87
+
88
+ Example of a database driven view layer:
89
+
90
+ <h1><%= Configurator[:login_page, :headline] %></h1>
91
+
92
+ <p><%= Configurator[:login_page, :username] %></p>
93
+ <%= text_field_tag :username %>
94
+
95
+ <p><%= Configurator[:login_page, :password] %></p>
96
+ ...
97
+
98
+ Simply store the above values, and you're now able to quickly attach a form to those different values, and satisfy your clients need to have every single aspect of the application. I'm sure there are plugins that do just this, but this is an example of Configurators global reach.
99
+
100
+
101
+ == Setup
102
+
103
+ - Install the gem
104
+ - Run the config_table generator
105
+ - Migrate your database
106
+ - Include Configurator into the models you need it in, and that's it.
107
+
108
+ If you need to be able to store complex objects, or strings greater than 255 characters, change the 'value' column to text. You can add to the ConfigurationHash class:
109
+
110
+ serialize :value
111
+
112
+ I haven't tried this yet, but it should work fine.
113
+
114
+ Copyright (c) 2009 Brennan Dunn, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |s|
8
+ s.name = "configurator"
9
+ s.summary = %Q{Fatten your models with key/value pairs}
10
+ s.email = "me@brennandunn.com"
11
+ s.homepage = "http://github.com/brennandunn/configurator"
12
+ s.description = "Fatten your models with key/value pairs"
13
+ s.authors = ["Brennan Dunn"]
14
+ s.files = %w(MIT-LICENSE README.rdoc Rakefile) + Dir.glob("{lib,test,generators}/**/*")
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'lib' << 'test'
23
+ t.pattern = 'test/**/*_test.rb'
24
+ t.verbose = false
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |t|
30
+ t.libs << 'test'
31
+ t.test_files = FileList['test/**/*_test.rb']
32
+ t.verbose = true
33
+ end
34
+ rescue LoadError
35
+ puts "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+
38
+ task :default => :test
@@ -0,0 +1,11 @@
1
+ class ConfigTableGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template "migration/create_config_table.rb", "db/migrate"
5
+ end
6
+ end
7
+
8
+ def file_name
9
+ "create_config_table"
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ class CreateConfigTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :config do |t|
4
+ t.references :associated, :polymorphic => true
5
+ t.string :namespace
6
+ t.string :key, :limit => 40, :null => false
7
+ t.string :value
8
+ t.string :data_type, :limit => 40
9
+ end
10
+ end
11
+
12
+ def self.down
13
+ drop_table :config
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ class ConfigurationHash < ActiveRecord::Base
2
+ set_table_name 'config'
3
+
4
+ belongs_to :associated, :polymorphic => true
5
+
6
+ class << self
7
+
8
+ def find_by_key_and_owner(call_type, key, owner, namespace = nil)
9
+ options = namespace ? { :namespace => namespace } : {}
10
+ find(:first, :conditions => { :key => key,
11
+ :associated_id => call_type == :instance ? owner.id : nil,
12
+ :associated_type => call_type == :instance ? owner.class.name : owner.name
13
+ }.merge(options))
14
+ end
15
+
16
+ def find_all_by_owner(call_type, owner, namespace = nil)
17
+ options = namespace ? { :namespace => namespace } : {}
18
+ find(:all, :conditions => { :associated_id => call_type == :instance ? owner.id : nil,
19
+ :associated_type => call_type == :instance ? owner.class.name : owner.name
20
+ }.merge(options))
21
+ end
22
+
23
+ end
24
+
25
+ def value=(param)
26
+ write_attribute :value, param.to_s
27
+ end
28
+
29
+ def value
30
+ if key.ends_with? "?"
31
+ read_attribute(:value) == "true"
32
+ else
33
+ read_attribute(:value)
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,145 @@
1
+ module Configurator
2
+ mattr_accessor :config
3
+
4
+ def self.[](*keys)
5
+ self.config ||= ConfigProxy.new(:class, self)
6
+ self.config[*keys]
7
+ end
8
+
9
+ def self.[]=(*keys)
10
+ self.config ||= ConfigProxy.new(:class, self)
11
+ value = *keys.pop
12
+ self.config[*keys] = value
13
+ end
14
+
15
+ def self.from_hash(hsh)
16
+ self.config ||= ConfigProxy.new(:class, self)
17
+ self.config.from_hash(hsh)
18
+ self.config
19
+ end
20
+
21
+ module ClassMethods
22
+
23
+ def default_configuration(hsh = {})
24
+ hsh.symbolize_keys!
25
+ @@default_configuration = hsh
26
+ hsh.each do |key, value|
27
+ config[key] = value
28
+ end
29
+ end
30
+
31
+ def get_default_configuration
32
+ @@default_configuration rescue {}
33
+ end
34
+
35
+ def config
36
+ @config_proxy ||= ConfigProxy.new(:class, self)
37
+ end
38
+
39
+ def config=(hsh)
40
+ config.from_hash(hsh)
41
+ config
42
+ end
43
+
44
+ end
45
+
46
+ module InstanceMethods
47
+
48
+ def config
49
+ @config_proxy ||= ConfigProxy.new(:instance, self)
50
+ end
51
+
52
+ def config=(hsh)
53
+ config.from_hash(hsh)
54
+ config
55
+ end
56
+
57
+ end
58
+
59
+ class ConfigProxy
60
+
61
+ attr_reader :defaults
62
+
63
+ def initialize(call_type, reference)
64
+ @reference, @call_type = reference, call_type
65
+ case call_type
66
+ when :instance
67
+ @options = { :associated_id => reference.id, :associated_type => reference.class.name }
68
+ @defaults = reference.class.get_default_configuration rescue {}
69
+ when :class
70
+ @options = { :associated_type => reference.name }
71
+ @defaults = reference.get_default_configuration rescue {}
72
+ end
73
+ end
74
+
75
+ def [](*keys)
76
+ namespace, key = [keys].flatten
77
+ if key.nil?
78
+ key = namespace
79
+ namespace = nil
80
+ end
81
+
82
+ pair = ConfigurationHash.find_by_key_and_owner(@call_type, key.to_s, @reference, namespace ? namespace.to_s : nil)
83
+ if pair.nil?
84
+ namespace ? @defaults[namespace][key] : @defaults[key] rescue nil
85
+ else
86
+ pair.value
87
+ end
88
+
89
+ end
90
+
91
+ def []=(*keys)
92
+ if keys.size == 3
93
+ namespace, key = keys[0], keys[1]
94
+ value = keys[2]
95
+ else
96
+ key, value = keys[0], keys[1]
97
+ end
98
+
99
+ pair = ConfigurationHash.find_by_key_and_owner(@call_type, key.to_s, @reference, namespace ? namespace.to_s : nil)
100
+ unless pair
101
+ pair = ConfigurationHash.new(@options)
102
+ pair.key, pair.value = key.to_s, value
103
+ pair.namespace = namespace.to_s if namespace
104
+ pair.save
105
+ else
106
+ pair.value = value
107
+ pair.save
108
+ end
109
+ value
110
+ end
111
+
112
+ def namespace(ns)
113
+ configs = ConfigurationHash.find_all_by_owner(@call_type, @reference, ns ? ns.to_s : nil)
114
+ configs.inject({}) { |hsh, c| hsh[c.key.intern] = c.value; hsh }
115
+ end
116
+
117
+ def to_hash(with_defaults = false)
118
+ Hash[ *ConfigurationHash.find_all_by_owner(@reference).map { |pair| [pair.key, pair.value] }.flatten ]
119
+ end
120
+
121
+ def from_hash(hsh)
122
+ hsh.each do |key, value|
123
+ if value.is_a?(Hash)
124
+ namespace = key
125
+ value.each do |key, value|
126
+ self[namespace, key] = value
127
+ end
128
+ else
129
+ self[key] = value
130
+ end
131
+ end
132
+ end
133
+
134
+ def method_missing(method, *args, &block)
135
+ self[method.to_s]
136
+ end
137
+
138
+ end
139
+
140
+ def self.included(receiver)
141
+ receiver.extend ClassMethods
142
+ receiver.send :include, InstanceMethods
143
+ end
144
+
145
+ end
@@ -0,0 +1,2 @@
1
+ require 'configurator/configurator'
2
+ require 'configurator/configuration_hash'
@@ -0,0 +1,95 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+
3
+ class ConfiguratorTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ setup_db
7
+ @user = User.create
8
+ @company = Company.create
9
+ end
10
+
11
+ context 'setting configurator values on objects' do
12
+
13
+ should 'not accept either strings or symbols as keys' do
14
+ @user.config[:favorite_color] = 'red'
15
+ assert_equal 'red', @user.config[:favorite_color]
16
+ @user.config['favorite_city'] = 'New York'
17
+ assert_equal 'New York', @user.config[:favorite_city]
18
+ end
19
+
20
+ should 'test for TRUE values when supplying an inquiry key' do
21
+ @user.config[:likes_cats?] = 'true'
22
+ assert_equal true, @user.config[:likes_cats?]
23
+ @user.config[:likes_dogs?] = true
24
+ assert_equal true, @user.config[:likes_dogs?]
25
+ end
26
+
27
+ should 'handle two levels of namespaces as keys' do
28
+ @other_user = User.create
29
+ @user.config[:animals, :likes_cats?] = true
30
+ @user.config[:animals, :favorite] = 'cat'
31
+ @other_user.config[:animals, :likes_cats?] = false
32
+ @other_user.config[:animals, :favorite] = 'dog'
33
+ assert_equal true, @user.config[:animals, :likes_cats?]
34
+ assert_equal 'cat', @user.config[:animals, :favorite]
35
+ assert_equal false, @other_user.config[:animals, :likes_cats?]
36
+ assert_equal 'dog', @other_user.config[:animals, :favorite]
37
+ assert_equal true, @user.config.namespace(:animals)[:likes_cats?]
38
+ assert_equal 'cat', @user.config.namespace(:animals)[:favorite]
39
+ assert_equal false, @other_user.config.namespace(:animals)[:likes_cats?]
40
+ assert_equal 'dog', @other_user.config.namespace(:animals)[:favorite]
41
+ end
42
+
43
+ should 'return a hash when querying a namespace with #namespace' do
44
+ @user.config[:animals, :cat] = 'Toby'
45
+ @user.config[:animals, :dog] = 'Gabby'
46
+ @user.config[:animals, :mouse] = 'Mickey'
47
+ hsh = { :cat => 'Toby', :dog => 'Gabby', :mouse => 'Mickey' }
48
+ assert_equal hsh, @user.config.namespace(:animals)
49
+ end
50
+
51
+ context 'when querying and attempting to write to a key that is a namespace' do
52
+
53
+ end
54
+
55
+ should 'honor default configuration settings' do
56
+ assert_equal '$55,000', @company.config[:salary, :default_for_manager]
57
+ @company.config[:salary, :default_for_manager] = '$65,000'
58
+ assert_equal '$65,000', @company.config[:salary, :default_for_manager]
59
+ end
60
+
61
+ should 'allow for mass assignment of flat/nested hashes' do
62
+ hash = { :favorite_color => 'red', :favorite_city => 'New York', :favorite_artist => 'Radiohead', :animals => { :favorite => 'cat', :likes_elephants? => true } }
63
+ @user.config = hash
64
+ assert_equal @user.config[:favorite_color], 'red'
65
+ assert_equal @user.config[:animals, :favorite], 'cat'
66
+ assert_equal @user.config[:animals, :likes_elephants?], true
67
+ end
68
+
69
+ should 'support default values on a class' do
70
+ User.config[:default_salary] = '$55,000'
71
+ assert_equal '$55,000', User.config[:default_salary]
72
+ end
73
+
74
+ should 'support default namespaced values on a class' do
75
+ assert_equal Company.config[:salary, :default_for_manager], '$55,000'
76
+ end
77
+
78
+ should 'allow for mass assignment on a class' do
79
+ hash = { :favorite_color => 'red', :favorite_city => 'New York', :favorite_artist => 'Radiohead', :animals => { :favorite => 'cat', :likes_elephants? => true } }
80
+ User.config = hash
81
+ assert_equal User.config[:favorite_color], 'red'
82
+ assert_equal User.config[:animals, :favorite], 'cat'
83
+ assert_equal User.config[:animals, :likes_elephants?], true
84
+ end
85
+
86
+ should 'support setting global values' do
87
+ Configurator[:enable_quantum_accelerator?] = true
88
+ assert Configurator[:enable_quantum_accelerator?]
89
+ Configurator[:colors, :favorite] = 'red'
90
+ assert_equal Configurator[:colors, :favorite], 'red'
91
+ end
92
+
93
+ end
94
+
95
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,52 @@
1
+ begin
2
+ require File.dirname(__FILE__) + '/../../../../config/environment'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'activerecord'
6
+ end
7
+ require 'test/unit'
8
+ require 'shoulda'
9
+ require 'mocha'
10
+
11
+ $LOAD_PATH.unshift(File.dirname(__FILE__)+'/../lib')
12
+ require 'configurator'
13
+ require 'configuration_hash'
14
+
15
+ class User < ActiveRecord::Base
16
+ include Configurator
17
+ end
18
+
19
+ class Company < ActiveRecord::Base
20
+ include Configurator
21
+
22
+ default_configuration :notify_users? => true, :salary => { :default_for_manager => '$55,000', :default_for_employee => '$25,000' }
23
+ end
24
+
25
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
26
+
27
+ def setup_db
28
+ ::ActiveRecord::Base.class_eval do
29
+ silence do
30
+ connection.create_table :config, :force => true do |t|
31
+ t.references :associated, :polymorphic => true
32
+ t.string :namespace
33
+ t.string :key, :limit => 40, :null => false
34
+ t.string :value
35
+ end
36
+
37
+ connection.create_table :users, :force => true do |t|
38
+ t.string :handle
39
+ end
40
+
41
+ connection.create_table :companies, :force => true do |t|
42
+ t.string :name
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def teardown_db
49
+ ActiveRecord::Base.connection.tables.each do |table|
50
+ ActiveRecord::Base.connection.drop_table(table)
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kb-configurator
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brennan Dunn
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-13 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Fatten your models with key/value pairs
17
+ email: me@brennandunn.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - MIT-LICENSE
26
+ - README.rdoc
27
+ - Rakefile
28
+ - generators/config_table/config_table_generator.rb
29
+ - generators/config_table/templates/migration/create_config_table.rb
30
+ - lib/configurator.rb
31
+ - lib/configurator/configuration_hash.rb
32
+ - lib/configurator/configurator.rb
33
+ - test/configurator_test.rb
34
+ - test/helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/brennandunn/configurator
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: Fatten your models with key/value pairs
63
+ test_files:
64
+ - test/configurator_test.rb
65
+ - test/helper.rb