tpitale-constant_cache 0.1.1

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) 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,99 @@
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.1.0:
32
+
33
+ In order to support DataMapper as well as ActiveRecord, and to reduce dependencies
34
+ in the gem itself we've decided to make ConstantCache a module that must be included
35
+ in the class you wish to call cache_constants on. If you wish to have all AR classes
36
+ include the module, simply add an initializer to do ActiveRecord::Base.send(:include,
37
+ ConstantCache)
38
+
39
+ The cache now call #all instead of #find(:all) because it is supported by both ORMs.
40
+
41
+ The only other major change in the is a change in the method name #caches_constants
42
+ to #cache_constants (cache is a verb, implying an action or event)
43
+
44
+ ## Installation
45
+
46
+ This code is packaged as a gem, so simply use the `gem` command to install:
47
+
48
+ gem install constant_cache
49
+
50
+ ## Example
51
+
52
+ "Out of the box," the constant_cache gem assumes that you want to use the 'name' column to generate
53
+ constants from a column called 'name' in your database table. Assuming this schema:
54
+
55
+ create_table :account_statuses do |t|
56
+ t.string :name, :description
57
+ end
58
+
59
+ AccountStatus.create!(:name => 'Active', :description => 'Active user account')
60
+ AccountStatus.create!(:name => 'Pending', :description => 'Pending user account')
61
+ AccountStatus.create!(:name => 'Disabled', :description => 'Disabled user account')
62
+
63
+ We can use the plugin to cache the data in the table:
64
+
65
+ class AccountStatus # uses ActiveRecord or DataMapper
66
+ include ConstantCache
67
+
68
+ cache_constants
69
+ end
70
+
71
+ Now you can write code that's a little cleaner and not use multiple unnecessary find calls:
72
+
73
+ Account.new(:username => 'preagan', :status => AccountStatus::PENDING)
74
+
75
+ If the column you want to use as the constant isn't 'name', you can set that in the model. If
76
+ we have :name, :slug, and :description, we can use 'slug' instead:
77
+
78
+ class AccountStatus # uses ActiveRecord or DataMapper
79
+ include ConstantCache
80
+
81
+ cache_constants :key => :slug
82
+ end
83
+
84
+ The value for the constant is truncated at 64 characters by default, but you can adjust this as
85
+ well:
86
+
87
+ class AccountStatus # uses ActiveRecord or DataMapper
88
+ include ConstantCache
89
+
90
+ cache_constants :limit => 16
91
+ end
92
+
93
+ ## Acknowlegements
94
+
95
+ Thanks to Dave Thomas for inspiring me to write this during his Metaprogramming talk at a Rails Edge
96
+ conference in early 2007.
97
+
98
+ Copyright (c) 2009 [Patrick Reagan](mailto:patrick.reagan@viget.com) and [Tony Pitale](mailto:tony.pitale@viget.com)
99
+ of Viget Labs, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+
5
+ require 'lib/constant_cache/version'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = "constant_cache"
9
+ s.version = ConstantCache::Version.to_s
10
+ s.has_rdoc = true
11
+ s.extra_rdoc_files = %w(README.md)
12
+ s.summary = ""
13
+ s.authors = ["Patrick Reagan", "Tony Pitale"]
14
+ s.email = "patrick.reagan@viget.com"
15
+ s.homepage = "http://www.viget.com/extend/"
16
+ s.files = %w(MIT-LICENSE README.md Rakefile) + Dir.glob("{lib,test}/**/*")
17
+
18
+ # s.add_dependency('gem', '~> version')
19
+ end
20
+
21
+ Rake::GemPackageTask.new(spec) do |pkg|
22
+ pkg.gem_spec = spec
23
+ end
24
+
25
+ Rake::TestTask.new do |t|
26
+ t.libs << 'test'
27
+ t.test_files = FileList["test/**/*_test.rb"]
28
+ t.verbose = true
29
+ end
30
+
31
+ desc 'Generate the gemspec to serve this Gem from Github'
32
+ task :github do
33
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
34
+ File.open(file, 'w') {|f| f << spec.to_ruby }
35
+ puts "Created gemspec: #{file}"
36
+ end
37
+
38
+ begin
39
+ require 'rcov/rcovtask'
40
+
41
+ desc "Generate RCov coverage report"
42
+ Rcov::RcovTask.new(:rcov) do |t|
43
+ t.test_files = FileList['test/**/*_test.rb']
44
+ t.rcov_opts << "-x lib/constant_cache.rb -x lib/constant_cache/version.rb"
45
+ end
46
+ rescue LoadError
47
+ nil
48
+ end
49
+
50
+ task :default => :test
@@ -0,0 +1,79 @@
1
+ module ConstantCache
2
+
3
+ CHARACTER_LIMIT = 64
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ #
10
+ # Error raised when duplicate constant defined
11
+ #
12
+ class DuplicateConstantError < StandardError; end
13
+
14
+ module ClassMethods
15
+ #
16
+ # The caches_constants method is the core of the functionality behind this mix-in. It provides
17
+ # a simple interface to cache the data in the corresponding table as constants on the model:
18
+ #
19
+ # class Status
20
+ # caches_constants
21
+ # end
22
+ #
23
+ # It makes certain assumptions about your schema: the constant created is based off of a <tt>name</tt>
24
+ # column in the database and long names are truncated to ConstantCache::CHARACTER_LIMIT characters before
25
+ # being set as constants. If there is a 'Pending' status in the database, you will now have a
26
+ # Status::PENDING constant that points to an instance of ActiveRecord or DataMapper::Resource.
27
+ #
28
+ # Beyond the basics, some configuration is allowed. You can change both the column that is used to generate
29
+ # the constant and the truncation length:
30
+ #
31
+ # class State
32
+ # include ConstantCache
33
+ #
34
+ # caches_constants :key => :abbreviation, :limit => 2
35
+ # end
36
+ #
37
+ # This will use the <tt>abbreviation</tt> column to generate constants and will truncate constant names to 2
38
+ # characters at the maximum.
39
+ #
40
+ # Note: In the event that a generated constant conflicts with an existing constant, a
41
+ # ConstantCache::DuplicateConstantError is raised.
42
+ #
43
+ def cache_constants(opts = {})
44
+ key = opts.fetch(:key, :name)
45
+ limit = opts.fetch(:limit, CHARACTER_LIMIT)
46
+ limit = CHARACTER_LIMIT unless limit > 0
47
+
48
+ @cache_options = {:key => key, :limit => limit}
49
+
50
+ all.each {|instance| instance.set_instance_as_constant }
51
+ end
52
+
53
+ def cache_options
54
+ @cache_options
55
+ end
56
+ end
57
+
58
+ #
59
+ # Create a constant on the class that pointing to an instance
60
+ #
61
+ def set_instance_as_constant
62
+ unless constant_name.nil?
63
+ if self.class.const_defined?(constant_name)
64
+ msg = "Constant #{self.class.to_s}::#{constant_name} has already been defined"
65
+ raise ConstantCache::DuplicateConstantError, msg
66
+ else
67
+ self.class.const_set(constant_name, self)
68
+ end
69
+ end
70
+ end
71
+
72
+ def constant_name #:nodoc:
73
+ name = self.send(self.class.cache_options[:key].to_sym)
74
+ if name.is_a?(String) && name != ''
75
+ @constant_name ||= name.constant_name[0,self.class.cache_options[:limit]]
76
+ end
77
+ end
78
+ private :constant_name
79
+ 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,12 @@
1
+ module ConstantCache
2
+
3
+ module Version #:nodoc:
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 1
7
+
8
+ def self.to_s # :nodoc:
9
+ [MAJOR, MINOR, TINY].join('.')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,2 @@
1
+ require 'constant_cache/core_ext'
2
+ require 'constant_cache/cache_methods'
@@ -0,0 +1,81 @@
1
+ require File.dirname(__FILE__) + '/../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.stubs(:all).returns([])
14
+ Cached.cache_constants
15
+ Cached.cache_options.should == {:key => :name, :limit => 64}
16
+ end
17
+
18
+ should "all overridden options for key and character limit" do
19
+ Cached.stubs(:all).returns([])
20
+ Cached.cache_constants(:key => :abbreviation, :limit => 20)
21
+ Cached.cache_options.should == {:key => :abbreviation, :limit => 20}
22
+ end
23
+
24
+ should "revert the limit on characters if less than 1" do
25
+ Cached.stubs(:all).returns([])
26
+ Cached.cache_constants(:limit => -10)
27
+ Cached.cache_options.should == {:key => :name, :limit => 64}
28
+ end
29
+
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
+ Cached.cache_constants
41
+ end
42
+ end
43
+
44
+ context "An instance of a class with ConstantCache mixed in" do
45
+ setup do
46
+ Cached.stubs(:all).returns([])
47
+ Cached.cache_constants(:limit => 20)
48
+ @cached = Cached.new
49
+ end
50
+
51
+ should "create a constant as a reference to the instance" do
52
+ @cached.name = 'al sharpton'
53
+ @cached.set_instance_as_constant
54
+ Cached.constants.include?("AL_SHARPTON").should == true
55
+ Cached::AL_SHARPTON.should == @cached
56
+ end
57
+
58
+ should "not create a constant without a key value" do
59
+ size = Cached.constants.size
60
+ @cached.set_instance_as_constant
61
+ Cached.constants.size.should == size
62
+ end
63
+
64
+ should "raise an exception on duplicate constant" do
65
+ @cached.name = 'buffalo'
66
+ assert_raises ConstantCache::DuplicateConstantError do
67
+ @cached.set_instance_as_constant
68
+ @cached.set_instance_as_constant
69
+ end
70
+ end
71
+
72
+ should "truncate long constant names" do
73
+ constant_name = ('a'*20).upcase
74
+
75
+ @cached.name = 'a'*65
76
+ @cached.set_instance_as_constant
77
+
78
+ Cached.constants.include?(constant_name).should == true
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/../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,9 @@
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
+ require File.dirname(__FILE__) + '/../lib/constant_cache'
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tpitale-constant_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Patrick Reagan
8
+ - Tony Pitale
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-12-15 00:00:00 -05:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description:
18
+ email: patrick.reagan@viget.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.md
25
+ files:
26
+ - MIT-LICENSE
27
+ - README.md
28
+ - Rakefile
29
+ - lib/constant_cache/cache_methods.rb
30
+ - lib/constant_cache/core_ext.rb
31
+ - lib/constant_cache/version.rb
32
+ - lib/constant_cache.rb
33
+ - test/constant_cache/cache_methods_test.rb
34
+ - test/constant_cache/core_ext_test.rb
35
+ - test/test_helper.rb
36
+ has_rdoc: true
37
+ homepage: http://www.viget.com/extend/
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.3.5
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: ""
64
+ test_files: []
65
+