cached_values 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +3 -0
- data/MIT-LICENSE +20 -0
- data/Manifest.txt +17 -0
- data/README.txt +66 -0
- data/Rakefile +17 -0
- data/init.rb +3 -0
- data/install.rb +1 -0
- data/lib/cached_values/cached_value.rb +145 -0
- data/lib/cached_values/caches_value.rb +104 -0
- data/lib/cached_values.rb +9 -0
- data/tasks/cached_values_tasks.rake +4 -0
- data/test/database.yml +18 -0
- data/test/leprechaun.rb +17 -0
- data/test/schema.rb +22 -0
- data/test/test_cached_values.rb +118 -0
- data/test/test_helper.rb +36 -0
- data/uninstall.rb +1 -0
- metadata +82 -0
data/History.txt
ADDED
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/Manifest.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
History.txt
|
2
|
+
MIT-LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
init.rb
|
7
|
+
install.rb
|
8
|
+
lib/cached_values.rb
|
9
|
+
lib/cached_values/cached_value.rb
|
10
|
+
lib/cached_values/caches_value.rb
|
11
|
+
tasks/cached_values_tasks.rake
|
12
|
+
test/database.yml
|
13
|
+
test/leprechaun.rb
|
14
|
+
test/schema.rb
|
15
|
+
test/test_cached_values.rb
|
16
|
+
test/test_helper.rb
|
17
|
+
uninstall.rb
|
data/README.txt
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= Cached Values
|
2
|
+
|
3
|
+
* http://github.com/JackDanger/cached_values/
|
4
|
+
|
5
|
+
A dead-simple way to calculate any value via Ruby or SQL and (optionally) have it saved in a database field.
|
6
|
+
|
7
|
+
== REQUIREMENTS:
|
8
|
+
|
9
|
+
* ObjectProxy
|
10
|
+
|
11
|
+
== INSTALL:
|
12
|
+
|
13
|
+
* as gem: sudo gem install cached_values
|
14
|
+
* as plugin: ./script/plugin install git://github.com/JackDanger/cached_values.git
|
15
|
+
|
16
|
+
== USAGE:
|
17
|
+
|
18
|
+
You can calculate values with a single line in any ActiveRecord model. To actually save those values you'll need to create
|
19
|
+
an attribute in your schema. By default the cached_value instance will look for an attribute with the same name as itself.
|
20
|
+
You can override this by specifying a :cache => :some_attribute option
|
21
|
+
|
22
|
+
A very simple case in which cached_values works just like the .count method on a has_many association:
|
23
|
+
|
24
|
+
class Leprechaun < ActiveRecord::Base
|
25
|
+
caches_value :total_gold_coins, :sql => 'select count(*) from gold_coins where leprechaun_id = #{id}'
|
26
|
+
end
|
27
|
+
|
28
|
+
Company.find(4).total_employees # => 45
|
29
|
+
|
30
|
+
A more sophisticated example:
|
31
|
+
|
32
|
+
class Leprechaun < ActiveRecord::Base
|
33
|
+
has_many :lucky_charms
|
34
|
+
has_many :deceived_children, :through => :lucky_charms
|
35
|
+
caches_value :total_children_remaining_to_be_deceived, :sql => '... very complicated sql here ...'
|
36
|
+
end
|
37
|
+
|
38
|
+
Leprechaun.find(14).total_children_remaining_to_be_deceived # => 6,692,243,122
|
39
|
+
|
40
|
+
The values can be of any type. The plugin will attempt to cast SQL results to the type corresponding with their database cache
|
41
|
+
but calculations in Ruby are left alone.
|
42
|
+
|
43
|
+
You can also calculate the value in Ruby using a string to be eval'ed or a Proc. Both are evaluated
|
44
|
+
in the context of the record instance.
|
45
|
+
|
46
|
+
class Leprechaun < ActiveRecord::Base
|
47
|
+
caches_value :total_gold, :eval => "some_archaic_and_silly_calculation(self.gold_coins)"
|
48
|
+
caches_value :total_lucky_charms, :eval => Proc.new {|record| record.calculate_total_lucky_charms }
|
49
|
+
end
|
50
|
+
|
51
|
+
The cache is customizable, you can specify which attribute should be used as a cache:
|
52
|
+
|
53
|
+
caches_value :runic_formula, :sql => '...' # uses 'full_formula' column if it exists
|
54
|
+
caches_value :standard_deviation_of_gold_over_time, # uses 'std' column if it exists
|
55
|
+
:sql => '...', :cache => 'std'
|
56
|
+
caches_value :id, :sql => '...', :cache => false # does NOT save any cache. This avoids overwriting an attribute
|
57
|
+
|
58
|
+
ActiveRecord callbacks can be used to call load (make sure the value is in memory), clear (flush the cache and
|
59
|
+
delete the instance), or reload (flush cache and reload instance) at certain times:
|
60
|
+
|
61
|
+
caches_value :standard_deviation, :sql => '...', :reload => [:after_save, :before_validation]
|
62
|
+
caches_value :full_formula, :sql => '...', :clear => :after_find
|
63
|
+
|
64
|
+
Bugs can be filed at http://code.lighthouseapp.com/projects/4502-hoopla-rails-plugins/
|
65
|
+
|
66
|
+
Copyright (c) 2007 Jack Danger Canty @ http://6brand.com, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
4
|
+
$:.unshift(File.dirname(__FILE__) + '/lib/cached_values')
|
5
|
+
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'hoe'
|
9
|
+
require "cached_values"
|
10
|
+
|
11
|
+
Hoe.new('cached_values', CachedValues::VERSION) do |p|
|
12
|
+
p.rubyforge_name = 'cachedvalues' # if different than lowercase project name
|
13
|
+
p.remote_rdoc_dir = '' # Release to root
|
14
|
+
p.developer('Jack Danger Canty', 'rubygems_cached_values@6brand.com')
|
15
|
+
end
|
16
|
+
|
17
|
+
# vim: syntax=Ruby
|
data/init.rb
ADDED
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
puts IO.read(File.join(File.dirname(__FILE__), 'README.txt'))
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'object_proxy'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class CachedValue < ObjectProxy
|
5
|
+
|
6
|
+
def initialize(owner, reflection)
|
7
|
+
@owner, @reflection = owner, reflection
|
8
|
+
reset
|
9
|
+
end
|
10
|
+
|
11
|
+
def reset
|
12
|
+
@target = nil
|
13
|
+
@loaded = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def load
|
17
|
+
reset
|
18
|
+
load_target
|
19
|
+
end
|
20
|
+
|
21
|
+
def reload
|
22
|
+
@owner.instance_variable_set("@#{@reflection.name}", nil)
|
23
|
+
reset
|
24
|
+
@target = find_target(true)
|
25
|
+
@owner.send @reflection.name
|
26
|
+
end
|
27
|
+
|
28
|
+
alias update reload
|
29
|
+
|
30
|
+
def clear
|
31
|
+
clear_cache
|
32
|
+
@owner.instance_variable_set("@#{@reflection.name}", nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
def loaded?
|
36
|
+
@loaded
|
37
|
+
end
|
38
|
+
|
39
|
+
def loaded
|
40
|
+
@loaded = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def target
|
44
|
+
@target
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def load_target
|
50
|
+
return nil unless defined?(@loaded)
|
51
|
+
@target = find_target unless loaded?
|
52
|
+
@loaded = true
|
53
|
+
@target
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_target(skip_cache = false)
|
57
|
+
target = find_target_from_cache unless skip_cache
|
58
|
+
unless target
|
59
|
+
target ||= @reflection.options[:sql] ? find_target_by_sql : find_target_by_eval
|
60
|
+
update_cache(target)
|
61
|
+
end
|
62
|
+
target
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_target_from_cache
|
66
|
+
@owner.send(:read_attribute, cache_column) if has_cache?
|
67
|
+
end
|
68
|
+
|
69
|
+
def find_target_by_sql
|
70
|
+
sql = sanitize_sql(interpolate_sql(@reflection.options[:sql]))
|
71
|
+
result = @owner.class.connection.select_value(sql)
|
72
|
+
result = typecast_result(result)
|
73
|
+
result
|
74
|
+
end
|
75
|
+
|
76
|
+
def find_target_by_eval
|
77
|
+
if @reflection.options[:eval].is_a?(String)
|
78
|
+
eval(@reflection.options[:eval], @owner.send(:binding))
|
79
|
+
elsif @reflection.options[:eval].is_a?(Proc)
|
80
|
+
@reflection.options[:eval].call(@owner)
|
81
|
+
elsif @reflection.options[:eval].is_a?(Symbol)
|
82
|
+
@owner.send @reflection.options[:eval]
|
83
|
+
else
|
84
|
+
raise ArgumentError.new("The :eval option on a cached_values must be a String or a Proc or a Symbol")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def cache_column
|
89
|
+
@reflection.options[:cache]
|
90
|
+
end
|
91
|
+
|
92
|
+
def has_cache?
|
93
|
+
@reflection.options[:cache] && @owner.attribute_names.include?(@reflection.options[:cache].to_s)
|
94
|
+
end
|
95
|
+
|
96
|
+
def clear_cache
|
97
|
+
update_cache(nil)
|
98
|
+
end
|
99
|
+
|
100
|
+
def update_cache(value)
|
101
|
+
return unless has_cache?
|
102
|
+
unless @owner.new_record?
|
103
|
+
@owner.class.update_all(["#{cache_column} = ?", value], ["id = ?", @owner.id])
|
104
|
+
end
|
105
|
+
@owner.send(:write_attribute, cache_column, value)
|
106
|
+
end
|
107
|
+
|
108
|
+
def typecast_result(result)
|
109
|
+
if has_cache?
|
110
|
+
typecast_sql_result_by_cache_column_type(result)
|
111
|
+
else
|
112
|
+
if result =~ /^\d+\.\d+$/
|
113
|
+
result.to_f
|
114
|
+
elsif result =~ /^\d+$/
|
115
|
+
result.to_i
|
116
|
+
else
|
117
|
+
result
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def typecast_sql_result_by_cache_column_type(result)
|
123
|
+
type = @owner.column_for_attribute(cache_column).type
|
124
|
+
case type
|
125
|
+
when :integer
|
126
|
+
result.to_i
|
127
|
+
when :float
|
128
|
+
result.to_f
|
129
|
+
when :boolean
|
130
|
+
[0,'0', 'NULL', 'nil', 'false', '', 'FALSE', 'False'].include?(result)
|
131
|
+
else
|
132
|
+
result
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def interpolate_sql(sql, record = nil)
|
137
|
+
@owner.send(:interpolate_sql, sql, record)
|
138
|
+
end
|
139
|
+
|
140
|
+
def sanitize_sql(sql)
|
141
|
+
@owner.class.send(:sanitize_sql, sql)
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/cached_value')
|
3
|
+
|
4
|
+
module CachesValues # :nodoc:
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
unless base.extended_by.include?(CachesValues::ClassMethods)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# USAGE:
|
14
|
+
#
|
15
|
+
# a very simple case in which cached_values works just like the .count method on a has_many association:
|
16
|
+
#
|
17
|
+
# class Company < ActiveRecord::Base
|
18
|
+
# caches_value :total_employees, :sql => 'select count(*) from employees where company_id = #{id}'
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# a more sophisticated example:
|
22
|
+
#
|
23
|
+
# class User < ActiveRecord::Base
|
24
|
+
# has_many :trinkets
|
25
|
+
# has_many :sales, :through => :trinkets
|
26
|
+
# caches_value :remaining_trinket_sales_allotted, :sql => '... very complicated sql here ...'
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# user = User.find(:first)
|
30
|
+
# user.remaining_trinket_sales_allotted # => 70
|
31
|
+
# Trinket.delete_all # <= any operation that would affect our value
|
32
|
+
# user.remaining_trinket_sales_allotted # => 70
|
33
|
+
# user.remaining_trinket_sales_allotted.reload # => 113
|
34
|
+
#
|
35
|
+
# You can also calculate the value in Ruby. This can be done by a string to be eval'ed or a Proc. Both are evaluated
|
36
|
+
# in the context of the record instance.
|
37
|
+
#
|
38
|
+
# class User < ActiveRecord::Base
|
39
|
+
# caches_value :expensive_calculation, :eval => "some_big_expensize_calculation(self.id)"
|
40
|
+
# caches_value :other_expensive_process, :eval => Proc.new {|record| record.other_expensize_process }
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
|
44
|
+
def caches_value(name, options = {})
|
45
|
+
reflection = create_cached_value_reflection(name, options)
|
46
|
+
|
47
|
+
configure_dependency_for_cached_value(reflection)
|
48
|
+
|
49
|
+
reflection.options[:cache] ||= reflection.name unless false == options[:cache]
|
50
|
+
|
51
|
+
cached_value_accessor_method(reflection, ActiveRecord::CachedValue)
|
52
|
+
cached_value_callback_methods(reflection)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def configure_dependency_for_cached_value(reflection)
|
58
|
+
|
59
|
+
if !reflection.options[:sql] && !reflection.options[:eval]
|
60
|
+
raise ArgumentError, "You must specify either the :eval or :sql options for caches_value in #{self.name}"
|
61
|
+
end
|
62
|
+
|
63
|
+
if reflection.options[:sql] && reflection.options[:eval]
|
64
|
+
raise ArgumentError, ":eval and :sql are mutually exclusive options. You may specify one or the other for caches_value in #{self.name}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_cached_value_reflection(name, options)
|
69
|
+
options.assert_valid_keys(:sql, :eval, :cache, :clear, :load, :reload)
|
70
|
+
|
71
|
+
reflection = ActiveRecord::Reflection::MacroReflection.new(:cached_value, name, options, self)
|
72
|
+
write_inheritable_hash :reflections, name => reflection
|
73
|
+
reflection
|
74
|
+
end
|
75
|
+
|
76
|
+
def cached_value_accessor_method(reflection, association_proxy_class)
|
77
|
+
define_method(reflection.name) do |*params|
|
78
|
+
force_reload = params.first unless params.empty?
|
79
|
+
association = instance_variable_get("@#{reflection.name}")
|
80
|
+
|
81
|
+
if association.nil? || force_reload
|
82
|
+
association = association_proxy_class.new(self, reflection)
|
83
|
+
instance_variable_set("@#{reflection.name}", association)
|
84
|
+
force_reload ? association.reload : association.load
|
85
|
+
end
|
86
|
+
association.target.nil? ? nil : association
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def cached_value_callback_methods(reflection)
|
91
|
+
%w{clear reload}.each do |operation|
|
92
|
+
if events = reflection.options[operation.to_sym]
|
93
|
+
events = [events] unless events.is_a?(Array)
|
94
|
+
events.map! { |event| event.to_s }
|
95
|
+
ActiveRecord::Callbacks::CALLBACKS.each do |callback|
|
96
|
+
if events.include?(callback)
|
97
|
+
send(callback, Proc.new {|record| record.send(reflection.name).send(operation)})
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/cached_values/cached_value")
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + "/cached_values/caches_value")
|
4
|
+
|
5
|
+
module CachedValues # :nodoc:
|
6
|
+
VERSION = '1.0.0'
|
7
|
+
end
|
8
|
+
|
9
|
+
ActiveRecord::Base.send :include, CachesValues
|
data/test/database.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
sqlite:
|
2
|
+
:adapter: sqlite
|
3
|
+
:dbfile: plugin.sqlite.db
|
4
|
+
sqlite3:
|
5
|
+
:adapter: sqlite3
|
6
|
+
:dbfile: ":memory:"
|
7
|
+
postgresql:
|
8
|
+
:adapter: postgresql
|
9
|
+
:username: postgres
|
10
|
+
:password: postgres
|
11
|
+
:database: plugin_test
|
12
|
+
:min_messages: ERROR
|
13
|
+
mysql:
|
14
|
+
:adapter: mysql
|
15
|
+
:host: localhost
|
16
|
+
:username: rails
|
17
|
+
:password:
|
18
|
+
:database: plugin_test
|
data/test/leprechaun.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Leprechaun < ActiveRecord::Base
|
2
|
+
caches_value :favorite_color_in_rot_13, :eval => Proc.new {|leprechaun| leprechaun.favorite_color.tr "A-Za-z", "N-ZA-Mn-za-m" }
|
3
|
+
caches_value :favorite_color_turned_uppercase, :eval => "favorite_color.upcase"
|
4
|
+
caches_value :number_of_gold_coins, :eval => :calculate_gold
|
5
|
+
caches_value :id_of_first_leprechaun_with_same_favorite_color, :sql => 'select id from leprechauns where leprechauns.favorite_color = "#{favorite_color}" and leprechauns.id <> #{id} limit 1'
|
6
|
+
caches_value :favorite_color_in_rot_13_without_cache, :eval => Proc.new {|leprechaun| leprechaun.favorite_color.tr "A-Za-z", "N-ZA-Mn-za-m" }, :cache => false
|
7
|
+
caches_value :favorite_color_turned_uppercase_with_explicit_cache, :eval => "favorite_color.upcase", :cache => 'some_other_cache_field'
|
8
|
+
caches_value :reload_callback, :eval => "rand(1000)", :reload => [:before_save, :after_validation]
|
9
|
+
caches_value :clear_callback, :eval => "rand(1000)", :clear => :before_validation
|
10
|
+
caches_value :float_cast, :sql => "select 82343.222"
|
11
|
+
caches_value :integer_cast, :sql => "select 19"
|
12
|
+
caches_value :string_cast, :sql => 'select "Top \'o the mornin\' to ya"'
|
13
|
+
|
14
|
+
def calculate_gold
|
15
|
+
'127 gold coins'
|
16
|
+
end
|
17
|
+
end
|
data/test/schema.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/leprechaun")
|
3
|
+
|
4
|
+
ActiveRecord::Schema.define(:version => 1) do
|
5
|
+
|
6
|
+
create_table :leprechauns do |t|
|
7
|
+
t.column :name, :string
|
8
|
+
t.column :favorite_color, :string
|
9
|
+
t.column :favorite_color_in_rot_13, :string
|
10
|
+
t.column :favorite_color_in_rot_13_without_cache, :string
|
11
|
+
t.column :favorite_color_turned_uppercase, :string
|
12
|
+
t.column :id_of_first_leprechaun_with_same_favorite_color, :integer
|
13
|
+
t.column :some_other_cache_field, :string
|
14
|
+
t.column :reload_callback, :float
|
15
|
+
t.column :clear_callback, :float
|
16
|
+
end
|
17
|
+
|
18
|
+
Leprechaun.create!(:name => 'Mc Nairn', :favorite_color => 'blue')
|
19
|
+
Leprechaun.create!(:name => "O' Houhlihan", :favorite_color => 'ochre')
|
20
|
+
Leprechaun.create!(:name => "Danger Cáinte", :favorite_color => 'blue')
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/test_helper")
|
3
|
+
|
4
|
+
class CachedValuesTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@mc_nairn = Leprechaun.find(:first)
|
8
|
+
@mc_nairn.favorite_color_in_rot_13.clear
|
9
|
+
@mc_nairn.favorite_color_turned_uppercase.clear
|
10
|
+
@mc_nairn.id_of_first_leprechaun_with_same_favorite_color.clear
|
11
|
+
@mc_nairn.favorite_color_in_rot_13_without_cache.clear
|
12
|
+
@mc_nairn.favorite_color_turned_uppercase_with_explicit_cache.clear
|
13
|
+
@mc_nairn.favorite_color = 'blue'
|
14
|
+
@mc_nairn.save!
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_proc_should_properly_calculate_value
|
18
|
+
assert_equal 'blue', @mc_nairn.favorite_color
|
19
|
+
assert_equal 'oyhr', @mc_nairn.favorite_color.tr("A-Za-z", "N-ZA-Mn-za-m")
|
20
|
+
assert_equal 'oyhr', @mc_nairn.favorite_color_in_rot_13
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_string_should_properly_calculate_value
|
24
|
+
assert_equal 'blue', @mc_nairn.favorite_color
|
25
|
+
assert_equal 'BLUE', @mc_nairn.favorite_color_turned_uppercase
|
26
|
+
@mc_nairn.update_attribute(:favorite_color, 'gold')
|
27
|
+
assert_equal 'BLUE', @mc_nairn.favorite_color_turned_uppercase
|
28
|
+
assert_equal 'GOLD', @mc_nairn.favorite_color_turned_uppercase.reload
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_symbol_should_calculate_value_from_method_call
|
32
|
+
assert_equal '127 gold coins', @mc_nairn.number_of_gold_coins
|
33
|
+
assert_equal '127 gold coins', @mc_nairn.calculate_gold
|
34
|
+
@mc_nairn.class.class_eval { def calculate_gold; '255 gold coins'; end }
|
35
|
+
assert_equal '255 gold coins', @mc_nairn.calculate_gold
|
36
|
+
assert_equal '127 gold coins', @mc_nairn.number_of_gold_coins
|
37
|
+
assert_equal '255 gold coins', @mc_nairn.number_of_gold_coins.reload
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_sql_should_properly_calculate_value
|
41
|
+
assert_equal 3, @mc_nairn.id_of_first_leprechaun_with_same_favorite_color
|
42
|
+
Leprechaun.find_by_name("O' Houhlihan").update_attribute(:favorite_color, 'blue')
|
43
|
+
assert_equal 3, @mc_nairn.id_of_first_leprechaun_with_same_favorite_color
|
44
|
+
assert_equal 2, @mc_nairn.id_of_first_leprechaun_with_same_favorite_color.reload
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_should_cache_value
|
48
|
+
assert_equal 'blue', @mc_nairn.favorite_color
|
49
|
+
assert_equal 'oyhr', @mc_nairn.favorite_color_in_rot_13
|
50
|
+
@mc_nairn.update_attribute(:favorite_color, 'red')
|
51
|
+
assert_equal 'erq', @mc_nairn.favorite_color.tr("A-Za-z", "N-ZA-Mn-za-m")
|
52
|
+
assert_equal 'oyhr', @mc_nairn.favorite_color_in_rot_13
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_cache_should_be_invalidated_on_clear
|
56
|
+
assert_equal 'blue', @mc_nairn.favorite_color
|
57
|
+
assert_equal 'oyhr', @mc_nairn.favorite_color_in_rot_13
|
58
|
+
@mc_nairn.favorite_color_in_rot_13.clear
|
59
|
+
assert_nil @mc_nairn.send(:read_attribute, :favorite_color_in_rot_13)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_value_should_be_updated_after_its_cleared
|
63
|
+
assert_equal 'blue', @mc_nairn.favorite_color
|
64
|
+
assert_equal 'oyhr', @mc_nairn.favorite_color_in_rot_13
|
65
|
+
@mc_nairn.update_attribute(:favorite_color, 'red')
|
66
|
+
assert_equal 'oyhr', @mc_nairn.favorite_color_in_rot_13
|
67
|
+
@mc_nairn.favorite_color_in_rot_13.clear
|
68
|
+
assert_equal 'erq', @mc_nairn.favorite_color_in_rot_13
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_should_not_cache_explicitly_noncaching_values
|
72
|
+
assert_equal 'blue', @mc_nairn.favorite_color
|
73
|
+
assert_equal 'oyhr', @mc_nairn.favorite_color_in_rot_13_without_cache
|
74
|
+
assert_nil @mc_nairn.send(:read_attribute, :favorite_color_in_rot_13_without_cache)
|
75
|
+
@mc_nairn.update_attribute(:favorite_color, 'red')
|
76
|
+
assert_equal 'erq', @mc_nairn.favorite_color.tr("A-Za-z", "N-ZA-Mn-za-m")
|
77
|
+
assert_equal 'erq', @mc_nairn.favorite_color_in_rot_13_without_cache.reload
|
78
|
+
assert_nil @mc_nairn.send(:read_attribute, :favorite_color_in_rot_13_without_cache)
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_should_respect_explicit_cache_column
|
82
|
+
assert_equal 'BLUE', @mc_nairn.favorite_color_turned_uppercase_with_explicit_cache
|
83
|
+
assert_equal 'BLUE', @mc_nairn.send(:read_attribute, :some_other_cache_field)
|
84
|
+
@mc_nairn.update_attribute(:favorite_color, 'red')
|
85
|
+
assert_equal 'BLUE', @mc_nairn.send(:read_attribute, :some_other_cache_field)
|
86
|
+
assert_equal 'RED', @mc_nairn.favorite_color_turned_uppercase_with_explicit_cache.reload
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_reload_callback_should_fire
|
90
|
+
value = @mc_nairn.reload_callback.to_s
|
91
|
+
assert_equal value.to_i, @mc_nairn.reload_callback
|
92
|
+
@mc_nairn.save!
|
93
|
+
assert_not_equal value.to_i, @mc_nairn.reload_callback.reload
|
94
|
+
value = @mc_nairn.reload_callback.to_s
|
95
|
+
assert_equal value.to_i, @mc_nairn.reload_callback
|
96
|
+
@mc_nairn.valid?
|
97
|
+
assert_not_equal value.to_i, @mc_nairn.reload_callback
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_clear_callback_should_fire
|
101
|
+
assert @mc_nairn.clear_callback
|
102
|
+
assert @mc_nairn.instance_variable_get("@clear_callback")
|
103
|
+
@mc_nairn.valid?
|
104
|
+
assert_nil @mc_nairn.instance_variable_get("@clear_callback")
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_sql_should_cast_to_integer
|
108
|
+
assert @mc_nairn.integer_cast.is_a?(Fixnum)
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_sql_should_cast_to_string
|
112
|
+
assert @mc_nairn.string_cast.is_a?(String)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_sql_should_cast_to_float
|
116
|
+
assert @mc_nairn.float_cast.is_a?(Float)
|
117
|
+
end
|
118
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Include this file in your test by copying the following line to your test:
|
2
|
+
# require File.expand_path(File.dirname(__FILE__) + "/test_helper")
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'test/unit'
|
8
|
+
require 'active_record'
|
9
|
+
require 'active_record/fixtures'
|
10
|
+
require "cached_values"
|
11
|
+
require File.expand_path(File.dirname(__FILE__) + "/leprechaun")
|
12
|
+
|
13
|
+
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
14
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
15
|
+
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
|
16
|
+
|
17
|
+
load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb")
|
18
|
+
|
19
|
+
class Test::Unit::TestCase #:nodoc:
|
20
|
+
|
21
|
+
# def create_fixtures(*table_names)
|
22
|
+
# if block_given?
|
23
|
+
# Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
|
24
|
+
# else
|
25
|
+
# Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
|
29
|
+
# Turn off transactional fixtures if you're working with MyISAM tables in MySQL
|
30
|
+
self.use_transactional_fixtures = true
|
31
|
+
|
32
|
+
# Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
|
33
|
+
self.use_instantiated_fixtures = false
|
34
|
+
|
35
|
+
# Add more helper methods to be used by all tests here...
|
36
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cached_values
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jack Danger Canty
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-03-07 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hoe
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.5.1
|
23
|
+
version:
|
24
|
+
description: ""
|
25
|
+
email:
|
26
|
+
- rubygems_cached_values@6brand.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- History.txt
|
33
|
+
- Manifest.txt
|
34
|
+
- README.txt
|
35
|
+
files:
|
36
|
+
- History.txt
|
37
|
+
- MIT-LICENSE
|
38
|
+
- Manifest.txt
|
39
|
+
- README.txt
|
40
|
+
- Rakefile
|
41
|
+
- init.rb
|
42
|
+
- install.rb
|
43
|
+
- lib/cached_values.rb
|
44
|
+
- lib/cached_values/cached_value.rb
|
45
|
+
- lib/cached_values/caches_value.rb
|
46
|
+
- tasks/cached_values_tasks.rake
|
47
|
+
- test/database.yml
|
48
|
+
- test/leprechaun.rb
|
49
|
+
- test/schema.rb
|
50
|
+
- test/test_cached_values.rb
|
51
|
+
- test/test_helper.rb
|
52
|
+
- uninstall.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/JackDanger/cached_values/
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --main
|
58
|
+
- README.txt
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: cachedvalues
|
76
|
+
rubygems_version: 1.0.1
|
77
|
+
signing_key:
|
78
|
+
specification_version: 2
|
79
|
+
summary: ""
|
80
|
+
test_files:
|
81
|
+
- test/test_cached_values.rb
|
82
|
+
- test/test_helper.rb
|