public
Description: Allows the caching of lookup data in your Rails models
Clone URL: git://github.com/vigetlabs/constant_cache.git
Search Repo:
Start of constant_cache plugin (for Advanced Rails Recipes)


git-svn-id: 
http://svn.extendviget.com/lab/trunk/plugins/caches_constants@122 
e959a6d6-1924-0410-92b3-a6fa492f4c66
reagent (author)
Mon Jun 25 21:11:51 -0700 2007
commit  29a604e21c87f1bb921471a132c6f3a82bb57a34
tree    8174e5493440e3cc92b1492c3e51e77c62be3c20
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0
@@ -0,0 +1,20 @@
0
+Copyright (c) 2007 [name of plugin creator]
0
+
0
+Permission is hereby granted, free of charge, to any person obtaining
0
+a copy of this software and associated documentation files (the
0
+"Software"), to deal in the Software without restriction, including
0
+without limitation the rights to use, copy, modify, merge, publish,
0
+distribute, sublicense, and/or sell copies of the Software, and to
0
+permit persons to whom the Software is furnished to do so, subject to
0
+the following conditions:
0
+
0
+The above copyright notice and this permission notice shall be
0
+included in all copies or substantial portions of the Software.
0
+
0
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
0
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
0
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
0
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
0
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
0
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
0
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
0
@@ -0,0 +1,69 @@
0
+CachesConstants
0
+===============
0
+
0
+When your database has tables that store lookup data, there is a tendency
0
+to provide those values as constants in the model. If you have an
0
+account_statuses table with a corresponding model, your constants may look
0
+like this:
0
+
0
+ class AccountStatus
0
+ ACTIVE = 1
0
+ PENDING = 2
0
+ DISABLE = 3
0
+ end
0
+
0
+There are a couple problems with this approach:
0
+
0
+As you add more lookup data to the table, you need to ensure that you're
0
+updating your models along with the data.
0
+
0
+The constants are stored as integer values and need to match up exactly
0
+with the data that's in the table (not necessarily a bad thing), but this
0
+solution forces you to write code like this:
0
+
0
+Account.new(:username => 'preagan', :status => AccountStatus.find(AccountStatus::PENDING))
0
+
0
+This requires multiple calls to find and obfuscates the code a bit. Since classes
0
+in Ruby are executable code, we can cache the objects from the database at runtime
0
+and use them in your application.
0
+
0
+
0
+Example
0
+=======
0
+
0
+The caches_constants plugin "out of the box" assumes that you want to generate
0
+constants from a column called 'name' in your database table. Assuming this schema:
0
+
0
+ create_table :account_statuses do |t|
0
+ t.string :name, :description
0
+ end
0
+
0
+ AccountStatus.create!(:name => 'Active', :description => 'Active user account')
0
+ AccountStatus.create!(:name => 'Pending', :description => 'Pending user account')
0
+ AccountStatus.create!(:name => 'Disabled', :description => 'Disabled user account')
0
+
0
+We can use the plugin to cache the data in the table:
0
+
0
+ class AccountStatus
0
+ caches_constants
0
+ end
0
+
0
+Now you can write code that's a little cleaner and not use multiple unnecessary find calls:
0
+
0
+ Account.new(:username => 'preagan', :status => AccountStatus::PENDING)
0
+
0
+If the column you want to use as the constant isn't 'name', you can set that in the model. If
0
+we have :name, :slug, and :description, we can use 'slug' instead:
0
+
0
+ class AccountStatus
0
+ caches_constants :key => :slug
0
+ end
0
+
0
+The value for the constant is truncated at 64 characters by default, but you can adjust this as
0
+well:
0
+
0
+ class AccountStatus
0
+ caches_constants :limit => 16
0
+ end
0
+
0
+Copyright (c) 2007 Patrick Reagan (patrick@viget.com), released under the MIT license
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0
@@ -0,0 +1,22 @@
0
+require 'rake'
0
+require 'rake/testtask'
0
+require 'rake/rdoctask'
0
+
0
+desc 'Default: run unit tests.'
0
+task :default => :test
0
+
0
+desc 'Test the caches_constants plugin.'
0
+Rake::TestTask.new(:test) do |t|
0
+ t.libs << 'lib'
0
+ t.pattern = 'test/**/*_test.rb'
0
+ t.verbose = true
0
+end
0
+
0
+desc 'Generate documentation for the caches_constants plugin.'
0
+Rake::RDocTask.new(:rdoc) do |rdoc|
0
+ rdoc.rdoc_dir = 'rdoc'
0
+ rdoc.title = 'CachesConstants'
0
+ rdoc.options << '--line-numbers' << '--inline-source'
0
+ rdoc.rdoc_files.include('README')
0
+ rdoc.rdoc_files.include('lib/**/*.rb')
0
+end
...
 
 
...
1
2
0
@@ -0,0 +1,2 @@
0
+require 'viget/format'
0
+require 'caches_constants'
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0
@@ -0,0 +1,15 @@
0
+module ActiveRecord
0
+ class Base
0
+ class << self
0
+ def caches_constants(additional_options = {})
0
+ options = {:key => :name, :limit => 64}.merge(additional_options)
0
+ find(:all).each do |model|
0
+ const = Viget::Format.to_const(model.send(options[:key].to_sym))
0
+ const = const[0, options[:limit]] unless const.blank?
0
+ set_constant = !const.blank? && !const_defined?(const)
0
+ const_set(const, model) if set_constant
0
+ end
0
+ end
0
+ end
0
+ end
0
+end
0
\ No newline at end of file
...
 
 
 
 
 
 
 
 
 
0
...
1
2
3
4
5
6
7
8
9
10
0
@@ -0,0 +1,9 @@
0
+module Viget
0
+ class Format
0
+ def self.to_const(string)
0
+ value = string.strip.gsub(/\s+/, '_').gsub(/[^\w_]/, '').upcase
0
+ value = nil if value.blank?
0
+ value
0
+ end
0
+ end
0
+end
0
\ No newline at end of file
...
 
 
 
 
...
1
2
3
4
0
@@ -0,0 +1,4 @@
0
+# desc "Explaining what the task does"
0
+# task :caches_constants do
0
+# # Task goes here
0
+# end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
0
@@ -0,0 +1,63 @@
0
+require File.dirname(__FILE__) + '/../test_helper'
0
+
0
+class BaseClass < ActiveRecord::Base
0
+ def self.columns; []; end
0
+end
0
+
0
+class SimpleClass < BaseClass; attr_accessor :name, :value; end
0
+class AlternateClass < BaseClass; attr_accessor :name2, :value; end
0
+
0
+class CacheAsConstantsTest < Test::Unit::TestCase
0
+
0
+ def test_cache_should_use_name_as_default
0
+ SimpleClass.expects(:find).returns([SimpleClass.new(:name => 'one', :value => 'pony')])
0
+ SimpleClass.caches_constants
0
+ assert_equal 'pony', SimpleClass::ONE.value
0
+ end
0
+
0
+ def test_cache_should_honor_alternate_accessor
0
+ AlternateClass.expects(:find).returns([AlternateClass.new(:name2 => 'foo bar', :value => 'pony')])
0
+ AlternateClass.caches_constants :key => :name2
0
+ assert_equal 'pony', AlternateClass::FOO_BAR.value
0
+ end
0
+
0
+ def test_cache_with_nil_key_value_should_not_set_constant
0
+ constant_count = SimpleClass.constants.size
0
+ SimpleClass.expects(:find).returns([SimpleClass.new(:name => '?', :value => 'nothing')])
0
+ SimpleClass.caches_constants
0
+ assert_equal constant_count, SimpleClass.constants.size
0
+ end
0
+
0
+ def test_cache_with_duplicate_constant_name_should_use_original
0
+ SimpleClass.expects(:find).returns([SimpleClass.new(:name => 'duplicate', :value => 'original'), SimpleClass.new(:name => 'duplicate', :value => 'new')])
0
+ SimpleClass.caches_constants
0
+ assert_equal 'original', SimpleClass::DUPLICATE.value
0
+ end
0
+
0
+ def test_cache_with_long_value_should_truncate_at_default_length
0
+ SimpleClass.expects(:find).returns([SimpleClass.new(:name => 'a' * 65, :value => 'overflow')])
0
+ SimpleClass.caches_constants
0
+ assert !SimpleClass.const_defined?('A' * 65)
0
+ assert_equal 'overflow', SimpleClass.const_get('A' * 64).value
0
+ end
0
+
0
+ def test_cache_with_specified_limit_should_truncate_to_length_specified
0
+ SimpleClass.expects(:find).returns([SimpleClass.new(:name => 'abcdef', :value => 'foo')])
0
+ SimpleClass.caches_constants :limit => 3
0
+ assert_equal 'foo', SimpleClass::ABC.value
0
+ end
0
+
0
+ def test_cache_with_invalid_limit_should_not_set_constant
0
+ constant_count = SimpleClass.constants.size
0
+ SimpleClass.expects(:find).returns([SimpleClass.new(:name => 'invalid', :value => 'one')])
0
+ SimpleClass.caches_constants :limit => 0
0
+ assert_equal constant_count, SimpleClass.constants.size
0
+ end
0
+
0
+ def test_cache_with_truncated_value_and_limit_should_not_overwrite_constant
0
+ SimpleClass.expects(:find).returns([SimpleClass.new(:name => 'abcdef', :value => 'one'), SimpleClass.new(:name => 'abggh', :value => 'two')])
0
+ SimpleClass.caches_constants :limit => 2
0
+ assert_equal 'one', SimpleClass::AB.value
0
+ end
0
+
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
0
@@ -0,0 +1,25 @@
0
+require File.dirname(__FILE__) + '/../test_helper'
0
+
0
+class FormatTest < Test::Unit::TestCase
0
+
0
+ def test_to_const_should_uppercase_characters
0
+ assert_equal 'TEST', Viget::Format.to_const('test')
0
+ end
0
+
0
+ def test_to_const_should_replace_whitespace_with_single_underscore
0
+ assert_equal 'TEST_THIS_FORMAT_PLEASE', Viget::Format.to_const("test this format\tplease")
0
+ end
0
+
0
+ def test_to_const_should_remove_leading_and_trailing_whitespace
0
+ assert_equal 'TEST', Viget::Format.to_const(' test ')
0
+ end
0
+
0
+ def test_to_const_should_remove_non_word_characters
0
+ assert_equal 'TEST', Viget::Format.to_const('!test.?')
0
+ end
0
+
0
+ def test_to_const_should_return_nil_if_all_chars_removed
0
+ assert_nil Viget::Format.to_const('?')
0
+ end
0
+
0
+end
0
\ No newline at end of file
...
 
 
 
 
 
 
 
 
0
...
1
2
3
4
5
6
7
8
9
0
@@ -0,0 +1,8 @@
0
+lib_dir = File.dirname(__FILE__) + '/lib'
0
+require File.dirname(__FILE__) + '/../../../config/boot'
0
+require 'rubygems'
0
+require 'test/unit'
0
+require 'mocha'
0
+require 'active_record'
0
+require "#{lib_dir}/viget/format"
0
+require "#{lib_dir}/caches_constants"
0
\ No newline at end of file

Comments

    No one has commented yet.