cached_values 1.0.1 → 1.7.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/README.markdown ADDED
@@ -0,0 +1,65 @@
1
+ Cached Values
2
+ =
3
+
4
+ [http://github.com/JackDanger/cached_values/](http://github.com/JackDanger/cached_values/)
5
+
6
+ Cache the result of any Ruby expression or SQL into an ActiveRecord attribute. Expire and update automatically.
7
+
8
+ INSTALL:
9
+ ===
10
+
11
+ * as gem: sudo gem install cached_values
12
+ * as plugin: ./script/plugin install git://github.com/JackDanger/cached_values.git
13
+
14
+ USAGE:
15
+ ===
16
+
17
+ You can calculate values with a single line in any ActiveRecord model. To actually save those values you'll need to create
18
+ an attribute in your schema. By default the cached_value instance will look for an attribute with the same name as itself.
19
+ You can override this by specifying a :cache => :some_attribute option
20
+
21
+ A very simple case in which cached_values works just like the .count method on a has_many association:
22
+
23
+ class Leprechaun < ActiveRecord::Base
24
+ caches_value :total_gold_coins, :sql => 'select count(*) from gold_coins where leprechaun_id = #{id}'
25
+ end
26
+
27
+ Company.find(4).total_employees # => 45
28
+
29
+ A more sophisticated example:
30
+
31
+ class Leprechaun < ActiveRecord::Base
32
+ has_many :lucky_charms
33
+ has_many :deceived_children, :through => :lucky_charms
34
+ caches_value :total_children_remaining_to_be_deceived, :sql => '... very complicated sql here ...'
35
+ end
36
+
37
+ Leprechaun.find(14).total_children_remaining_to_be_deceived # => 6,692,243,122
38
+
39
+ The values can be of any type. The plugin will attempt to cast SQL results to the type corresponding with their database cache
40
+ but calculations in Ruby are left alone.
41
+
42
+ You can also calculate the value in Ruby using a string to be eval'ed or a Proc. Both are evaluated
43
+ in the context of the record instance.
44
+
45
+ class Leprechaun < ActiveRecord::Base
46
+ caches_value :total_gold, :eval => "some_archaic_and_silly_calculation(gold_coins)"
47
+ caches_value :total_lucky_charms, :eval => Proc.new {|record| record.calculate_total_lucky_charms }
48
+ end
49
+
50
+ The cache is customizable, you can specify which attribute should be used as a cache:
51
+
52
+ caches_value :runic_formula, :sql => '...' # uses 'runic_formula' column if it exists
53
+ caches_value :standard_deviation_of_gold_over_time, # uses 'std' column if it exists
54
+ :sql => '...', :cache => 'std'
55
+ caches_value :id, :sql => '...', :cache => false # does NOT persist to db, just memoize in memory. This avoids overwriting the attribute of the same name.
56
+
57
+ ActiveRecord callbacks can be used to reload (update cache and reload instance) at certain times:
58
+
59
+ caches_value :standard_deviation, :sql => '...', :reload => [:before_save, :after_destroy]
60
+ caches_value :runic_formula, :sql => '...', :reload => :after_create
61
+
62
+
63
+ Patches welcome, forks celebrated.
64
+
65
+ Copyright (c) 2007 Jack Danger Canty @ [http://jåck.com](http://jåck.com), released under the MIT license
data/Rakefile CHANGED
@@ -1,17 +1,28 @@
1
- # -*- ruby -*-
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "cached_values"
5
+ gem.summary = %Q{Memoize and persist calculations into ActiveRecord attributes}
6
+ gem.description = %Q{Speedup your ActiveRecord by storing and updating the results of SQL or Ruby expressions into record attributes}
7
+ gem.email = "rubygems@6brand.com"
8
+ gem.homepage = "http://github.com/JackDanger/cached_values"
9
+ gem.authors = ["Jack Danger Canty"]
10
+ gem.add_dependency "object_proxy", ">= 0"
11
+ gem.add_development_dependency "active_record", ">= 0"
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
17
+ end
2
18
 
3
- $:.unshift(File.dirname(__FILE__) + '/lib')
4
- $:.unshift(File.dirname(__FILE__) + '/lib/cached_values')
5
19
 
6
20
 
7
- require 'rubygems'
8
- require 'hoe'
9
- require "cached_values"
21
+ task :default => :test
10
22
 
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')
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << '.'
26
+ test.pattern = 'test/*_test.rb'
27
+ test.verbose = true
15
28
  end
16
-
17
- # vim: syntax=Ruby
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.7.0
data/init.rb CHANGED
@@ -1,3 +1,2 @@
1
- require 'cached_value'
2
1
  require 'cached_values'
3
2
  ActiveRecord::Base.send :include, CachedValues
data/install.rb CHANGED
@@ -1 +1 @@
1
- puts IO.read(File.join(File.dirname(__FILE__), 'README.txt'))
1
+ puts IO.read(File.join(File.dirname(__FILE__), 'README.markdown'))
@@ -1,3 +1,4 @@
1
+ gem 'object_proxy'
1
2
  require 'object_proxy'
2
3
 
3
4
  module ActiveRecord
@@ -5,23 +6,16 @@ module ActiveRecord
5
6
 
6
7
  def initialize(owner, reflection)
7
8
  @owner, @reflection = owner, reflection
8
- reset
9
9
  end
10
10
 
11
- def reset
12
- @target = nil
13
- end
14
-
15
- def load
16
- reset
17
- @target = find_target(true)
11
+ def load(skip_cache = false)
12
+ @target = find_target skip_cache
18
13
  update_cache(@target)
14
+ self
19
15
  end
20
16
 
21
17
  def reload
22
- @owner.instance_variable_set("@#{@reflection.name}", nil)
23
- load
24
- @owner.send @reflection.name
18
+ @owner.send(@reflection.name).load(true)
25
19
  end
26
20
 
27
21
  alias update reload
@@ -29,6 +23,7 @@ module ActiveRecord
29
23
  def clear
30
24
  clear_cache
31
25
  @owner.instance_variable_set("@#{@reflection.name}", nil)
26
+ self
32
27
  end
33
28
 
34
29
  def target
@@ -82,10 +77,18 @@ module ActiveRecord
82
77
 
83
78
  def update_cache(value)
84
79
  return unless has_cache?
85
- unless @owner.new_record?
86
- @owner.class.update_all(["#{cache_column} = ?", value], ["id = ?", @owner.id])
87
- end
80
+
88
81
  @owner.send(:write_attribute, cache_column, value)
82
+
83
+ return if @owner.new_record? || !CachedValues.perform_save?
84
+
85
+ exist_conditions = value.nil? ?
86
+ [ "#{cache_column} IS NULL AND id = ?", @owner.id ] :
87
+ [ "(#{cache_column} = ? AND #{cache_column} IS NOT NULL) AND id = ?", value, @owner.id ]
88
+
89
+ return if @owner.class.exists? exist_conditions
90
+
91
+ @owner.class.update_all [ "#{cache_column} = ?", value], ["id = ?", @owner.id ]
89
92
  end
90
93
 
91
94
  def typecast_result(result)
data/lib/cached_values.rb CHANGED
@@ -1,9 +1,113 @@
1
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")
2
+ require File.expand_path(File.dirname(__FILE__) + "/cached_value")
4
3
 
5
4
  module CachedValues # :nodoc:
6
- VERSION = '1.0.1'
5
+ def self.perform_save?
6
+ @perform_save ||= true
7
+ end
8
+
9
+ def self.without_saving_record
10
+ @perform_save = false
11
+ yield
12
+ @perform_save = true
13
+ end
14
+
15
+ # USAGE:
16
+ #
17
+ # a very simple case in which cached_values works just like the .count method on a has_many association:
18
+ #
19
+ # class Company < ActiveRecord::Base
20
+ # caches_value :total_employees, :sql => 'select count(*) from employees where company_id = #{id}'
21
+ # end
22
+ #
23
+ # a more sophisticated example:
24
+ #
25
+ # class User < ActiveRecord::Base
26
+ # has_many :trinkets
27
+ # has_many :sales, :through => :trinkets
28
+ # caches_value :remaining_trinket_sales_allotted, :sql => '... very complicated sql here ...'
29
+ # end
30
+ #
31
+ # user = User.find(:first)
32
+ # user.remaining_trinket_sales_allotted # => 70
33
+ # Trinket.delete_all # <= any operation that would affect our value
34
+ # user.remaining_trinket_sales_allotted # => 70
35
+ # user.remaining_trinket_sales_allotted.reload # => 113
36
+ #
37
+ # 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
38
+ # in the context of the record instance.
39
+ #
40
+ # class User < ActiveRecord::Base
41
+ # caches_value :expensive_calculation, :eval => "some_big_expensize_calculation(self.id)"
42
+ # caches_value :other_expensive_process, :eval => Proc.new {|record| record.other_expensize_process }
43
+ # end
44
+ #
45
+
46
+ def caches_value(name, options = {})
47
+ reflection = create_cached_value_reflection(name, options)
48
+
49
+ configure_dependency_for_cached_value(reflection)
50
+
51
+ reflection.options[:cache] ||= reflection.name unless false == options[:cache]
52
+
53
+ cached_value_accessor_method(reflection, ActiveRecord::CachedValue)
54
+ cached_value_callback_methods(reflection)
55
+ end
56
+
57
+ private
58
+
59
+ def configure_dependency_for_cached_value(reflection)
60
+
61
+ if !reflection.options[:sql] && !reflection.options[:eval]
62
+ raise ArgumentError, "You must specify either the :eval or :sql options for caches_value in #{self.name}"
63
+ end
64
+
65
+ if reflection.options[:sql] && reflection.options[:eval]
66
+ raise ArgumentError, ":eval and :sql are mutually exclusive options. You may specify one or the other for caches_value in #{self.name}"
67
+ end
68
+ end
69
+
70
+ def create_cached_value_reflection(name, options)
71
+ options.assert_valid_keys(:sql, :eval, :cache, :clear, :load, :reload)
72
+
73
+ reflection = ActiveRecord::Reflection::MacroReflection.new(:cached_value, name, options, self)
74
+ write_inheritable_hash :reflections, name => reflection
75
+ reflection
76
+ end
77
+
78
+ def cached_value_accessor_method(reflection, association_proxy_class)
79
+ define_method(reflection.name) do
80
+ association = instance_variable_get("@#{reflection.name}")
81
+
82
+ if association.nil?
83
+ association = association_proxy_class.new(self, reflection)
84
+ instance_variable_set("@#{reflection.name}", association)
85
+ association.load
86
+ end
87
+ association.target.nil? ? nil : association
88
+ end
89
+ end
90
+
91
+ def cached_value_callback_methods(reflection)
92
+ if events = reflection.options[:reload]
93
+ events = Array(events).map { |event| event.to_s }
94
+ ActiveRecord::Callbacks::CALLBACKS.each do |callback|
95
+ if events.include?(callback)
96
+ # before_save do |record|
97
+ send callback do |record|
98
+ if %{before_save before_create before_destroy}.include?(callback.to_s)
99
+ CachedValues.without_saving_record do
100
+ record.send(reflection.name).reload
101
+ end
102
+ else
103
+ record.send(reflection.name).reload
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
7
110
  end
8
111
 
9
- ActiveRecord::Base.send :include, CachesValues
112
+
113
+ ActiveRecord::Base.extend CachedValues
@@ -1,4 +1,3 @@
1
- require 'test/unit'
2
1
  require File.expand_path(File.dirname(__FILE__) + "/test_helper")
3
2
 
4
3
  class CachedValuesTest < Test::Unit::TestCase
@@ -97,13 +96,6 @@ class CachedValuesTest < Test::Unit::TestCase
97
96
  assert_not_equal value.to_i, @mc_nairn.reload_callback
98
97
  end
99
98
 
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
99
  def test_sql_should_cast_to_integer
108
100
  assert @mc_nairn.integer_cast.is_a?(Fixnum)
109
101
  end
data/test/database.yml CHANGED
@@ -1,9 +1,9 @@
1
1
  sqlite:
2
2
  :adapter: sqlite
3
- :dbfile: plugin.sqlite.db
3
+ :database: plugin.sqlite.db
4
4
  sqlite3:
5
5
  :adapter: sqlite3
6
- :dbfile: ":memory:"
6
+ :database: ":memory:"
7
7
  postgresql:
8
8
  :adapter: postgresql
9
9
  :username: postgres
@@ -15,4 +15,4 @@ mysql:
15
15
  :host: localhost
16
16
  :username: rails
17
17
  :password:
18
- :database: plugin_test
18
+ :database: plugin_test
data/test/leprechaun.rb CHANGED
@@ -6,7 +6,6 @@ class Leprechaun < ActiveRecord::Base
6
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
7
  caches_value :favorite_color_turned_uppercase_with_explicit_cache, :eval => "favorite_color.upcase", :cache => 'some_other_cache_field'
8
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
9
  caches_value :float_cast, :sql => "select 82343.222"
11
10
  caches_value :integer_cast, :sql => "select 19"
12
11
  caches_value :string_cast, :sql => 'select "Top \'o the mornin\' to ya"'
data/test/test_helper.rb CHANGED
@@ -4,7 +4,9 @@
4
4
  $:.unshift(File.dirname(__FILE__) + '/../lib')
5
5
 
6
6
  require 'rubygems'
7
+ gem 'test-unit'
7
8
  require 'test/unit'
9
+ require 'active_support'
8
10
  require 'active_record'
9
11
  require 'active_record/fixtures'
10
12
  require "cached_values"
@@ -16,7 +18,7 @@ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
16
18
 
17
19
  load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb")
18
20
 
19
- class Test::Unit::TestCase #:nodoc:
21
+ class ActiveSupport::TestCase #:nodoc:
20
22
 
21
23
  # def create_fixtures(*table_names)
22
24
  # if block_given?
@@ -27,10 +29,10 @@ class Test::Unit::TestCase #:nodoc:
27
29
  # end
28
30
 
29
31
  # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
30
- self.use_transactional_fixtures = true
32
+ # self.use_transactional_fixtures = true
31
33
 
32
34
  # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
33
- self.use_instantiated_fixtures = false
35
+ # self.use_instantiated_fixtures = false
34
36
 
35
37
  # Add more helper methods to be used by all tests here...
36
- end
38
+ end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cached_values
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 7
8
+ - 0
9
+ version: 1.7.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Jack Danger Canty
@@ -9,74 +14,89 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2008-03-12 00:00:00 -07:00
17
+ date: 2010-05-03 00:00:00 -07:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
- name: hoe
17
- version_requirement:
18
- version_requirements: !ruby/object:Gem::Requirement
21
+ name: object_proxy
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
19
24
  requirements:
20
25
  - - ">="
21
26
  - !ruby/object:Gem::Version
22
- version: 1.5.1
23
- version:
24
- description: ""
25
- email:
26
- - rubygems_cached_values@6brand.com
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: active_record
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ description: Speedup your ActiveRecord by storing and updating the results of SQL or Ruby expressions into record attributes
45
+ email: rubygems@6brand.com
27
46
  executables: []
28
47
 
29
48
  extensions: []
30
49
 
31
50
  extra_rdoc_files:
32
- - History.txt
33
- - Manifest.txt
34
- - README.txt
51
+ - README.markdown
35
52
  files:
36
- - History.txt
37
53
  - MIT-LICENSE
38
- - Manifest.txt
39
- - README.txt
54
+ - README.markdown
40
55
  - Rakefile
56
+ - VERSION
41
57
  - init.rb
42
58
  - install.rb
59
+ - lib/cached_value.rb
43
60
  - lib/cached_values.rb
44
- - lib/cached_values/cached_value.rb
45
- - lib/cached_values/caches_value.rb
46
61
  - tasks/cached_values_tasks.rake
62
+ - test/cached_values_test.rb
47
63
  - test/database.yml
48
64
  - test/leprechaun.rb
49
65
  - test/schema.rb
50
- - test/test_cached_values.rb
51
66
  - test/test_helper.rb
52
67
  - uninstall.rb
53
68
  has_rdoc: true
54
- homepage: http://github.com/JackDanger/cached_values/
69
+ homepage: http://github.com/JackDanger/cached_values
70
+ licenses: []
71
+
55
72
  post_install_message:
56
73
  rdoc_options:
57
- - --main
58
- - README.txt
74
+ - --charset=UTF-8
59
75
  require_paths:
60
76
  - lib
61
77
  required_ruby_version: !ruby/object:Gem::Requirement
62
78
  requirements:
63
79
  - - ">="
64
80
  - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
65
83
  version: "0"
66
- version:
67
84
  required_rubygems_version: !ruby/object:Gem::Requirement
68
85
  requirements:
69
86
  - - ">="
70
87
  - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
71
90
  version: "0"
72
- version:
73
91
  requirements: []
74
92
 
75
- rubyforge_project: cachedvalues
76
- rubygems_version: 1.0.1
93
+ rubyforge_project:
94
+ rubygems_version: 1.3.6
77
95
  signing_key:
78
- specification_version: 2
79
- summary: ""
96
+ specification_version: 3
97
+ summary: Memoize and persist calculations into ActiveRecord attributes
80
98
  test_files:
81
- - test/test_cached_values.rb
99
+ - test/cached_values_test.rb
100
+ - test/leprechaun.rb
101
+ - test/schema.rb
82
102
  - test/test_helper.rb
data/History.txt DELETED
@@ -1,7 +0,0 @@
1
- === 1.0.1 / 2008-03-12
2
-
3
- * The find_target method is now public
4
-
5
- === 1.0.0 / 2008-03-06
6
-
7
- * Converted from a Rails plugin
data/Manifest.txt DELETED
@@ -1,17 +0,0 @@
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 DELETED
@@ -1,66 +0,0 @@
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
@@ -1,104 +0,0 @@
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