dm-constant-cache 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 tpitale
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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 [name of plugin creator]
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.md ADDED
@@ -0,0 +1,103 @@
1
+ # Constant Cache
2
+
3
+ When your database has tables that store lookup data, there is a tendency
4
+ to provide those values as constants in the model. If you have an
5
+ account_statuses table with a corresponding model, your constants may look
6
+ like this:
7
+
8
+ class AccountStatus
9
+ ACTIVE = 1
10
+ PENDING = 2
11
+ DISABLED = 3
12
+ end
13
+
14
+ There are a couple of problems with this approach:
15
+
16
+ As you add more lookup data to the table, you need to ensure that you're
17
+ updating your models along with the data.
18
+
19
+ The constants are stored as integer values and need to match up exactly
20
+ with the data that's in the table (not necessarily a bad thing), but this
21
+ solution forces you to write code like this:
22
+
23
+ Account.new(:username => 'preagan', :status => AccountStatus.find(AccountStatus::PENDING))
24
+
25
+ This requires multiple calls to find and obfuscates the code a bit. Since classes
26
+ in Ruby are executable code, we can cache the objects from the database at load time
27
+ and use them in your application.
28
+
29
+ ## Changes
30
+
31
+ ### Major changes in version 0.2.0:
32
+
33
+ Split versions for DM and AR. For dm use: gem 'dm-constant-cache', '0.2.0', :require => 'constant\_cache'
34
+
35
+ ### Major changes in version 0.1.0:
36
+
37
+ In order to support DataMapper as well as ActiveRecord, and to reduce dependencies
38
+ in the gem itself we've decided to make ConstantCache a module that must be included
39
+ in the class you wish to call cache_constants on. If you wish to have all AR classes
40
+ include the module, simply add an initializer to do ActiveRecord::Base.send(:include,
41
+ ConstantCache)
42
+
43
+ The cache now call #all instead of #find(:all) because it is supported by both ORMs.
44
+
45
+ The only other major change in the is a change in the method name #caches_constants
46
+ to #cache_constants (cache is a verb, implying an action or event)
47
+
48
+ ## Installation
49
+
50
+ This code is packaged as a gem, so simply use the `gem` command to install:
51
+
52
+ gem install constant_cache
53
+
54
+ ## Example
55
+
56
+ "Out of the box," the constant_cache gem assumes that you want to use the 'name' column to generate
57
+ constants from a column called 'name' in your database table. Assuming this schema:
58
+
59
+ create_table :account_statuses do |t|
60
+ t.string :name, :description
61
+ end
62
+
63
+ AccountStatus.create!(:name => 'Active', :description => 'Active user account')
64
+ AccountStatus.create!(:name => 'Pending', :description => 'Pending user account')
65
+ AccountStatus.create!(:name => 'Disabled', :description => 'Disabled user account')
66
+
67
+ We can use the plugin to cache the data in the table:
68
+
69
+ class AccountStatus # uses ActiveRecord or DataMapper
70
+ include ConstantCache
71
+
72
+ cache_constants
73
+ end
74
+
75
+ Now you can write code that's a little cleaner and not use multiple unnecessary find calls:
76
+
77
+ Account.new(:username => 'preagan', :status => AccountStatus::PENDING)
78
+
79
+ If the column you want to use as the constant isn't 'name', you can set that in the model. If
80
+ we have :name, :slug, and :description, we can use 'slug' instead:
81
+
82
+ class AccountStatus # uses ActiveRecord or DataMapper
83
+ include ConstantCache
84
+
85
+ cache_constants :key => :slug
86
+ end
87
+
88
+ The value for the constant is truncated at 64 characters by default, but you can adjust this as
89
+ well:
90
+
91
+ class AccountStatus # uses ActiveRecord or DataMapper
92
+ include ConstantCache
93
+
94
+ cache_constants :limit => 16
95
+ end
96
+
97
+ ## Acknowlegements
98
+
99
+ Thanks to Dave Thomas for inspiring me to write this during his Metaprogramming talk at a Rails Edge
100
+ conference in early 2007.
101
+
102
+ Copyright (c) 2009 [Patrick Reagan](mailto:patrick.reagan@viget.com) and [Tony Pitale](mailto:tony.pitale@viget.com)
103
+ of Viget Labs, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "dm-constant-cache"
8
+ gem.summary = ""
9
+ gem.description = ""
10
+ gem.authors = ["Tony Pitale", "Patrick Reagan"]
11
+ gem.email = "tony.pitale@viget.com"
12
+ gem.homepage = "http://www.viget.com/extend/"
13
+ gem.files = %w(MIT-LICENSE README.md Rakefile) + Dir.glob("{lib,test}/**/*")
14
+
15
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'test'
25
+ test.test_files = FileList["test/**/*_test.rb"]
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+
32
+ desc "Generate RCov coverage report"
33
+ Rcov::RcovTask.new(:rcov) do |t|
34
+ t.test_files = FileList['test/**/*_test.rb']
35
+ t.rcov_opts << "-x lib/constant_cache.rb"
36
+ end
37
+ rescue LoadError
38
+ nil
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "constant_cache #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
@@ -0,0 +1,82 @@
1
+ module ConstantCache
2
+
3
+ CHARACTER_LIMIT = 64
4
+
5
+ def self.included(model)
6
+ model.extend(ClassMethods)
7
+ (@descendants ||= []) << model
8
+ end
9
+
10
+ def self.cache!
11
+ @descendants.each {|klass| klass.all.each {|instance| instance.set_instance_as_constant }}
12
+ end
13
+
14
+ #
15
+ # Error raised when duplicate constant defined
16
+ #
17
+ class DuplicateConstantError < StandardError; end
18
+
19
+ module ClassMethods
20
+ #
21
+ # The caches_constants method is the core of the functionality behind this mix-in. It provides
22
+ # a simple interface to cache the data in the corresponding table as constants on the model:
23
+ #
24
+ # class Status
25
+ # caches_constants
26
+ # end
27
+ #
28
+ # It makes certain assumptions about your schema: the constant created is based off of a <tt>name</tt>
29
+ # column in the database and long names are truncated to ConstantCache::CHARACTER_LIMIT characters before
30
+ # being set as constants. If there is a 'Pending' status in the database, you will now have a
31
+ # Status::PENDING constant that points to an instance of ActiveRecord or DataMapper::Resource.
32
+ #
33
+ # Beyond the basics, some configuration is allowed. You can change both the column that is used to generate
34
+ # the constant and the truncation length:
35
+ #
36
+ # class State
37
+ # include ConstantCache
38
+ #
39
+ # caches_constants :key => :abbreviation, :limit => 2
40
+ # end
41
+ #
42
+ # This will use the <tt>abbreviation</tt> column to generate constants and will truncate constant names to 2
43
+ # characters at the maximum.
44
+ #
45
+ # Note: In the event that a generated constant conflicts with an existing constant, a
46
+ # ConstantCache::DuplicateConstantError is raised.
47
+
48
+ def cache_as(key)
49
+ self.cache_options[:key] = key
50
+ end
51
+
52
+ def cache_limit(limit)
53
+ self.cache_options[:limit] = (limit > 0 ? limit : CHARACTER_LIMIT)
54
+ end
55
+
56
+ def cache_options
57
+ # @cache_options = {:key => :name, :limit => CHARACTER_LIMIT}.merge(@cache_options || {})
58
+ @cache_options ||= {:key => :name, :limit => CHARACTER_LIMIT}
59
+ end
60
+
61
+ def reset_cache_options
62
+ @cache_options = {:key => :name, :limit => CHARACTER_LIMIT}
63
+ end
64
+ end
65
+
66
+ #
67
+ # Create a constant on the class that pointing to an instance
68
+ #
69
+ def set_instance_as_constant
70
+ unless constant_name.nil? || self.class.const_defined?(constant_name)
71
+ self.class.const_set(constant_name, self)
72
+ end
73
+ end
74
+
75
+ def constant_name #:nodoc:
76
+ name = self.send(self.class.cache_options[:key].to_sym)
77
+ if name.is_a?(String) && name != ''
78
+ @constant_name ||= name.constant_name[0,self.class.cache_options[:limit]]
79
+ end
80
+ end
81
+ private :constant_name
82
+ end
@@ -0,0 +1,15 @@
1
+ class String
2
+
3
+ #
4
+ # A method to create a constant name from the existing string. This method condenses
5
+ # multiple non-word characters into a single underscore:
6
+ #
7
+ # 'abc'.constant_name => 'ABC'
8
+ # 'Restaurants & Bars'.constant_name => 'RESTAURANTS_BARS'
9
+ #
10
+ def constant_name
11
+ value = self.strip.gsub(/\s+/, '_').gsub(/[^\w_]/, '').gsub(/_{2,}/, '_').upcase
12
+ (value == '') ? nil : value
13
+ end
14
+
15
+ end
@@ -0,0 +1,2 @@
1
+ require 'constant_cache/core_ext'
2
+ require 'constant_cache/cache_methods'
@@ -0,0 +1,80 @@
1
+ require 'test_helper'
2
+
3
+ class Cached
4
+ include ConstantCache
5
+
6
+ attr_accessor :name
7
+ attr_accessor :abbreviation
8
+ end
9
+
10
+ class CacheMethodsTest < Test::Unit::TestCase
11
+ context "A class with ConstantCache mixed in" do
12
+ should "have default options for the cache key and character limit" do
13
+ Cached.reset_cache_options
14
+ Cached.cache_options.should == {:key => :name, :limit => 64}
15
+ end
16
+
17
+ should "all overridden options for key and character limit" do
18
+ Cached.cache_as :abbreviation
19
+ Cached.cache_limit 20
20
+ Cached.cache_options.should == {:key => :abbreviation, :limit => 20}
21
+ end
22
+
23
+ should "revert the limit on characters if less than 1" do
24
+ Cached.cache_limit -10
25
+ Cached.cache_options.should == {:key => :name, :limit => 64}
26
+ end
27
+ end
28
+
29
+ context "ConstantCache" do
30
+ should "be able to cache all instances as constants" do
31
+ c1 = Cached.new
32
+ c1.name = 'al einstein'
33
+ c1.expects(:set_instance_as_constant)
34
+
35
+ c2 = Cached.new
36
+ c2.name = 'al franken'
37
+ c2.expects(:set_instance_as_constant)
38
+
39
+ Cached.expects(:all).returns([c1, c2])
40
+ ConstantCache.cache!
41
+ end
42
+ end
43
+
44
+ context "An instance of a class with ConstantCache mixed in" do
45
+ setup do
46
+ Cached.cache_limit 20
47
+ @cached = Cached.new
48
+ end
49
+
50
+ should "create a constant as a reference to the instance" do
51
+ @cached.name = 'al sharpton'
52
+ @cached.set_instance_as_constant
53
+ Cached.constants.include?("AL_SHARPTON").should == true
54
+ Cached::AL_SHARPTON.should == @cached
55
+ end
56
+
57
+ should "not create a constant without a key value" do
58
+ size = Cached.constants.size
59
+ @cached.set_instance_as_constant
60
+ Cached.constants.size.should == size
61
+ end
62
+
63
+ should "not raise an exception on duplicate constant" do
64
+ @cached.name = 'buffalo'
65
+ assert_nothing_raised do
66
+ @cached.set_instance_as_constant
67
+ @cached.set_instance_as_constant
68
+ end
69
+ end
70
+
71
+ should "truncate long constant names" do
72
+ constant_name = ('a'*20).upcase
73
+
74
+ @cached.name = 'a'*65
75
+ @cached.set_instance_as_constant
76
+
77
+ Cached.constants.include?(constant_name).should == true
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+
3
+ class CoreExtTest < Test::Unit::TestCase
4
+ context "The String core extension" do
5
+ should "upcase its characters" do
6
+ 'test'.constant_name.should == 'TEST'
7
+ end
8
+
9
+ should "replace whitespace with a single underscore" do
10
+ "test this \tformat\nplease.".constant_name.should == 'TEST_THIS_FORMAT_PLEASE'
11
+ end
12
+
13
+ should "remove leading and trailing whitespace" do
14
+ ' test '.constant_name.should == 'TEST'
15
+ end
16
+
17
+ should "remove non-word characters" do
18
+ '!test?'.constant_name.should == 'TEST'
19
+ end
20
+
21
+ should "not singularize plural name" do
22
+ 'tests'.constant_name.should == 'TESTS'
23
+ end
24
+
25
+ should "return nil when all characters are removed" do
26
+ '?'.constant_name.should be(nil)
27
+ end
28
+
29
+ should "collapse multiple underscores" do
30
+ 'test__me'.constant_name.should == 'TEST_ME'
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ $:.reject! { |e| e.include? 'TextMate' }
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'shoulda'
6
+ require 'matchy'
7
+ require 'mocha'
8
+
9
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
10
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
11
+ require 'constant_cache'
12
+
13
+ class Test::Unit::TestCase
14
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-constant-cache
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
+ platform: ruby
11
+ authors:
12
+ - Tony Pitale
13
+ - Patrick Reagan
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-21 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: thoughtbot-shoulda
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ description: ""
34
+ email: tony.pitale@viget.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - LICENSE
41
+ - README.md
42
+ files:
43
+ - MIT-LICENSE
44
+ - README.md
45
+ - Rakefile
46
+ - lib/constant_cache.rb
47
+ - lib/constant_cache/cache_methods.rb
48
+ - lib/constant_cache/core_ext.rb
49
+ - test/constant_cache/cache_methods_test.rb
50
+ - test/constant_cache/core_ext_test.rb
51
+ - test/test_helper.rb
52
+ - LICENSE
53
+ has_rdoc: true
54
+ homepage: http://www.viget.com/extend/
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.6
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: ""
83
+ test_files:
84
+ - test/constant_cache/cache_methods_test.rb
85
+ - test/constant_cache/core_ext_test.rb
86
+ - test/test_helper.rb