cached_enumeration 2.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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.travis.yml +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +52 -0
- data/Rakefile +7 -0
- data/cached_enumeration.gemspec +30 -0
- data/lib/cached_enumeration.rb +4 -0
- data/lib/cached_enumeration/cached_enumeration.rb +300 -0
- data/lib/cached_enumeration/version.rb +3 -0
- data/spec/lib/association_spec.rb +88 -0
- data/spec/lib/cached_enumeration_spec.rb +205 -0
- data/spec/spec_helper.rb +27 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bc6c4ec88760cf1e4287f68b9e5c201842568e40
|
4
|
+
data.tar.gz: b791be8345a7e0dea745bd04e589be8967d5f456
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ba861a401664cb286f09444df4ef66344eee6de70c9dca6e547c3376c0a4a7cdd6d9e4e0b182cfc6927e366935a231b568cf4ce1ec3eed6cbf4f5c9bf6c700c6
|
7
|
+
data.tar.gz: 5f58d6294394a1e284b20302fc16879660fbe3e9cfa6e1e5014782f4cc636952a9f634f7f3fc92c5c9e2e4c527019bd16bed1d3b8fe716058080a4c1be02287e
|
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
.idea
|
2
|
+
.ruby-version
|
3
|
+
.ruby-gemset
|
4
|
+
.rspec
|
5
|
+
*.gem
|
6
|
+
*.rbc
|
7
|
+
.bundle
|
8
|
+
.config
|
9
|
+
.yardoc
|
10
|
+
Gemfile.lock
|
11
|
+
InstalledFiles
|
12
|
+
_yardoc
|
13
|
+
coverage
|
14
|
+
doc/
|
15
|
+
lib/bundler/man
|
16
|
+
pkg
|
17
|
+
rdoc
|
18
|
+
spec/reports
|
19
|
+
test/tmp
|
20
|
+
test/version_tmp
|
21
|
+
tmp
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Peter Schrammel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# CachedEnumeration
|
2
|
+
|
3
|
+
Loads your active record objects into memory so you don't have to include them.
|
4
|
+
|
5
|
+
Currently only working for the ActiveRecord/Rails 4.1 series. See other branches for older Rails versions.
|
6
|
+
|
7
|
+
## Warning
|
8
|
+
Some methods (by_attributename, cached_all) were dropped as they are not needed. Forwardporting them should be
|
9
|
+
easy.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'cached_enumeration'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install cached_enumeration
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
class Geneder < ActiveRecord::Base
|
28
|
+
cache_enumeration :order => 'name', :hashed => [:id,:name], :constantize => true
|
29
|
+
|
30
|
+
|
31
|
+
Now the following situations are cached:
|
32
|
+
* `Gender.find_by(id: 1)`
|
33
|
+
* `Gender.find_by(name: 'male')`
|
34
|
+
* `Gender.where(name: 'male).first`
|
35
|
+
* `Gender.all`
|
36
|
+
* `Gender.order('name').all`
|
37
|
+
* `Gender::MALE`
|
38
|
+
* `Gender::FEMALE`
|
39
|
+
|
40
|
+
If a Profile belongs_to a Gender you can simply write:
|
41
|
+
`profile.gender`
|
42
|
+
end no DB query whill be executed.
|
43
|
+
|
44
|
+
|
45
|
+
## Contributing
|
46
|
+
|
47
|
+
1. Fork it
|
48
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
49
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
50
|
+
4. Write specs!
|
51
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
52
|
+
6. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cached_enumeration/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cached_enumeration"
|
8
|
+
spec.version = CachedEnumeration::VERSION
|
9
|
+
spec.authors = ["Peter Schrammel"]
|
10
|
+
spec.email = ["peter.schrammel@experteer.com"]
|
11
|
+
spec.description = %q{Cache nonchanging ActiveRecord models in memory}
|
12
|
+
spec.summary = %q{Cache nonchanging ActiveRecord models in memory}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec","~> 3.1"
|
24
|
+
spec.add_development_dependency "sqlite3","1.3.8"
|
25
|
+
# spec.add_development_dependency "byebug"
|
26
|
+
# spec.add_development_dependency "rubinius-debugger"
|
27
|
+
spec.add_dependency "activerecord","~> 4.1"
|
28
|
+
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,300 @@
|
|
1
|
+
module CachedEnumeration
|
2
|
+
=begin rdoc
|
3
|
+
provide cached access to enumeration values
|
4
|
+
|
5
|
+
usage: add cache_enumeration <params> to ActiveRecord class
|
6
|
+
|
7
|
+
parameters are
|
8
|
+
:order order of items in cached_all (default: 'id')
|
9
|
+
:hashed list of attributes to provide hashes for (default: [ 'id', 'name' ];
|
10
|
+
:hashed list of attributes to provide hashes for (default: [ 'id', 'name' ];
|
11
|
+
id will always be added to that list, if missing
|
12
|
+
:constantize attribute to provide constants for (default: 'name')
|
13
|
+
use nil, not to generate constants
|
14
|
+
|
15
|
+
cached methods are:
|
16
|
+
find_from_ids( <id> ) or find_from_ids( [ <id>, <id>, ... ] )
|
17
|
+
providing cached find( <id> ) or find( [ <id>, <id>, ... ] )
|
18
|
+
find_by_XY / by_XY for all hashed attributes (by_XY is deprecated)
|
19
|
+
cached_all
|
20
|
+
|
21
|
+
besides constants using the upcase name are set up providing the entries
|
22
|
+
|
23
|
+
note that all objects (arrays, maps and the models themselfs) are frozen
|
24
|
+
to avoid unintentional changes.
|
25
|
+
|
26
|
+
Cachability of enumerations does not imply that all enumeration access should
|
27
|
+
be cached. This is a question that needs to be well thought depending on the
|
28
|
+
size of the enumeration and the number of accesses to the cached data.
|
29
|
+
|
30
|
+
The by_XY finder should be avoided as the find_by_XY will be available with
|
31
|
+
and without cache.
|
32
|
+
=end
|
33
|
+
class Cache
|
34
|
+
attr_reader :options
|
35
|
+
|
36
|
+
def initialize(base, params)
|
37
|
+
# p params
|
38
|
+
@options=init_options(params)
|
39
|
+
@cache={} #cache by keys
|
40
|
+
@all=[] #cache of all
|
41
|
+
@status=:uncached #can be :uncached,:cashing,:cached
|
42
|
+
@klass=base
|
43
|
+
|
44
|
+
#base.extend(ClassMethods)
|
45
|
+
#base.reset_column_information
|
46
|
+
base_singleton = class << base;
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
patch_const_missing(base_singleton) if @options[:constantize]
|
51
|
+
#create_find_by_methods(base_singleton)
|
52
|
+
end
|
53
|
+
|
54
|
+
def all
|
55
|
+
ensure_caches
|
56
|
+
@all
|
57
|
+
end
|
58
|
+
|
59
|
+
#returns a value from a cache
|
60
|
+
#@param String att name of the attribute
|
61
|
+
#@param String key value of the attribute
|
62
|
+
def get_by(att, key)
|
63
|
+
ensure_caches
|
64
|
+
key=key.to_i if att.to_s == "id"
|
65
|
+
@cache[att.to_s][key]
|
66
|
+
end
|
67
|
+
|
68
|
+
def hashed_by?(att)
|
69
|
+
options[:hashed].include?(att.to_s)
|
70
|
+
end
|
71
|
+
|
72
|
+
#forces a cache
|
73
|
+
#@return Boolean true is it just cached, false if it was already cached
|
74
|
+
def cache!
|
75
|
+
#only load if loading not yet in progress
|
76
|
+
ensure_caches if @status == :uncached
|
77
|
+
end
|
78
|
+
|
79
|
+
def cached?
|
80
|
+
@status==:cached
|
81
|
+
end
|
82
|
+
|
83
|
+
def order
|
84
|
+
@options[:order]
|
85
|
+
end
|
86
|
+
|
87
|
+
def first
|
88
|
+
@all.first
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def ensure_caches
|
94
|
+
return false if cached? || caching?
|
95
|
+
@status=:caching
|
96
|
+
|
97
|
+
@cache = Hash.new do |hash, key|
|
98
|
+
hash[key]=Hash.new
|
99
|
+
end
|
100
|
+
|
101
|
+
# the next line is weird but I want to have to Array so I use select
|
102
|
+
# to dereference the relation
|
103
|
+
@all = @klass.order(@options[:order]).all.to_a.freeze
|
104
|
+
|
105
|
+
@all.each do |entry|
|
106
|
+
@options[:hashed].each do |att|
|
107
|
+
@cache[att.to_s][entry.send(att)] = entry.freeze
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
create_constants if @options[:constantize]
|
112
|
+
|
113
|
+
@klass.logger.try(:info, "Filled cache of #{@klass.name}: #{@options.inspect}")
|
114
|
+
@status=:cached
|
115
|
+
true
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def caching?
|
120
|
+
@status==:caching
|
121
|
+
end
|
122
|
+
|
123
|
+
def init_options(params)
|
124
|
+
defaults = {
|
125
|
+
:order => 'id',
|
126
|
+
:hashed => ['id', 'name'],
|
127
|
+
:constantize => 'name',
|
128
|
+
}
|
129
|
+
#params check logic
|
130
|
+
params_diff=params.keys - defaults.keys
|
131
|
+
raise ArgumentError.new("unexpected parameters #{params_diff.inspect}, only #{defaults.keys.inspect} are understood") unless params_diff.empty?
|
132
|
+
params = defaults.merge(params)
|
133
|
+
params[:hashed] << 'id' unless params[:hashed].include? 'id'
|
134
|
+
params[:hashed].map! do |name|
|
135
|
+
name.to_s
|
136
|
+
end
|
137
|
+
params
|
138
|
+
end
|
139
|
+
|
140
|
+
def create_constants
|
141
|
+
#puts "creating constants #{self.name}"
|
142
|
+
proc=@options[:constantize].respond_to?(:call)
|
143
|
+
|
144
|
+
@all.each do |model|
|
145
|
+
const_name = if proc
|
146
|
+
@options[:constantize].call(model).upcase
|
147
|
+
else
|
148
|
+
model.send(@options[:constantize]).upcase
|
149
|
+
end
|
150
|
+
|
151
|
+
@klass.const_set const_name, model
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def patch_const_missing(base_singleton)
|
156
|
+
# no class caching in derived classes!
|
157
|
+
return if @klass.parent.respond_to? :const_missing_with_cache_enumeration
|
158
|
+
@klass.extend ConstMissing
|
159
|
+
base_singleton.alias_method_chain :const_missing, :cache_enumeration
|
160
|
+
end
|
161
|
+
|
162
|
+
module ConstMissing
|
163
|
+
def const_missing_with_cache_enumeration(const_name)
|
164
|
+
if cache_enumeration.cache! #if we just cached
|
165
|
+
self.const_get(const_name) #try again
|
166
|
+
else
|
167
|
+
const_missing_without_cache_enumeration(const_name) #fails as usual
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
module ActiveRecord
|
175
|
+
class Relation
|
176
|
+
def to_a_with_cache_enumeration
|
177
|
+
res=nil
|
178
|
+
|
179
|
+
if cache_enumeration? && cache_enumeration.cached? && order_is_cached?
|
180
|
+
res=case
|
181
|
+
when just_modified?(:order)
|
182
|
+
#all and the order is cached?
|
183
|
+
cache_enumeration.all
|
184
|
+
when just_modified?(:limit, :order, :where)
|
185
|
+
case
|
186
|
+
when limit_value == 1 && where_values.blank?
|
187
|
+
# usually the #first case
|
188
|
+
[cache_enumeration.first]
|
189
|
+
when limit_value == 1 && where_values.present? && where_is_cached?
|
190
|
+
# usually "= 1" or "= ?" .first or find or find_by
|
191
|
+
[get_by_where]
|
192
|
+
when limit_value.blank? && where_values.present? && where_is_cached?
|
193
|
+
# usually the association case (where id in (1,2,56,6))
|
194
|
+
get_by_where
|
195
|
+
else
|
196
|
+
to_a_without_cache_enumeration #where is to complicated for us
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
if res #got a result the return it
|
203
|
+
res
|
204
|
+
else
|
205
|
+
cache_enumeration.cache! if cache_enumeration?
|
206
|
+
to_a_without_cache_enumeration
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
alias_method_chain :to_a, :cache_enumeration
|
212
|
+
|
213
|
+
def take_with_cache_enumeration
|
214
|
+
if cache_enumeration? && cache_enumeration.cached?
|
215
|
+
case
|
216
|
+
#when just_modified?(:limit)
|
217
|
+
# cache_enumeration.first #tsk the first value of the default order
|
218
|
+
when just_modified?(:where) && where_is_cached?
|
219
|
+
get_by_where
|
220
|
+
else
|
221
|
+
take_without_cache_enumeration
|
222
|
+
end
|
223
|
+
else
|
224
|
+
cache_enumeration.cache! if cache_enumeration?
|
225
|
+
take_without_cache_enumeration
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
alias_method_chain :take, :cache_enumeration
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
def get_by_where
|
234
|
+
att_name=where_values[0].left.name
|
235
|
+
identifier = where_values[0].right
|
236
|
+
|
237
|
+
if identifier.kind_of?(Array)
|
238
|
+
identifier.map do |id|
|
239
|
+
cache_enumeration.get_by(att_name, id)
|
240
|
+
end.compact
|
241
|
+
else
|
242
|
+
identifier=bind_values[0][1] if identifier=='?'
|
243
|
+
cache_enumeration.get_by(att_name, identifier)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# just one ascending order which is the same as the cached one or none
|
248
|
+
def order_is_cached?
|
249
|
+
order_values.empty? || (
|
250
|
+
order_values.size == 1 &&
|
251
|
+
((order_values[0].respond_to?(:ascending?) && order_values[0].ascending? &&
|
252
|
+
cache_enumeration.order == order_values[0].expr.name) ||
|
253
|
+
order_values[0] == cache_enumeration.order #sometimes the order is just as string
|
254
|
+
)
|
255
|
+
)
|
256
|
+
end
|
257
|
+
|
258
|
+
def where_is_cached?
|
259
|
+
where_values.size == 1 &&
|
260
|
+
where_values[0].kind_of?(Arel::Nodes::Node) &&
|
261
|
+
where_values[0].operator == :== &&
|
262
|
+
cache_enumeration.hashed_by?(where_values[0].left.name)
|
263
|
+
end
|
264
|
+
|
265
|
+
#*modified is an array like :limit, :where, :order, :select, :includes, :preload
|
266
|
+
#:readonly
|
267
|
+
def just_modified?(*modified)
|
268
|
+
return false if @klass.locking_enabled?
|
269
|
+
return false if limit_value.present? && !modified.include?(:limit)
|
270
|
+
return false if where_values.present? && !modified.include?(:where)
|
271
|
+
return false if order_values.present? && !modified.include?(:order)
|
272
|
+
return false if select_values.present? && !modified.include?(:select)
|
273
|
+
return false if includes_values.present? && !modified.include?(:includes)
|
274
|
+
return false if preload_values.present? && !modified.include?(:preload)
|
275
|
+
return false if readonly_value.present? && !modified.include?(:readonly)
|
276
|
+
return false if joins_values.present? && !modified.include?(:joins)
|
277
|
+
true
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
|
282
|
+
class Base
|
283
|
+
class << self
|
284
|
+
def cache_enumeration(params = {})
|
285
|
+
#p "init: #{params.inspect}"
|
286
|
+
if params.delete(:reset)
|
287
|
+
@cache_enumeration = nil
|
288
|
+
end
|
289
|
+
|
290
|
+
@cache_enumeration ||= CachedEnumeration::Cache.new(self, params)
|
291
|
+
|
292
|
+
end
|
293
|
+
|
294
|
+
def cache_enumeration?
|
295
|
+
@cache_enumeration.present?
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
#require 'logger'
|
4
|
+
|
5
|
+
def logged
|
6
|
+
logger = ActiveRecord::Base.logger
|
7
|
+
ActiveRecord::Base.logger=Logger.new(STDOUT)
|
8
|
+
ActiveRecord::Base.logger.level=Logger::DEBUG
|
9
|
+
yield
|
10
|
+
ActiveRecord::Base.logger=logger
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'association caching' do
|
14
|
+
before :all do
|
15
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
16
|
+
# ActiveRecord::Base.logger=Logger.new(STDOUT)
|
17
|
+
# ActiveRecord::Base.logger.level=Logger::DEBUG
|
18
|
+
|
19
|
+
ActiveRecord::Migration.create_table :genders do |t|
|
20
|
+
# t.integer :id
|
21
|
+
t.string :name
|
22
|
+
end
|
23
|
+
|
24
|
+
class Gender < ActiveRecord::Base
|
25
|
+
cache_enumeration
|
26
|
+
end
|
27
|
+
|
28
|
+
Gender.create!(:name => 'male')
|
29
|
+
Gender.create!(:name => 'female')
|
30
|
+
Gender.cache_enumeration.cache!
|
31
|
+
|
32
|
+
ActiveRecord::Migration.create_table :profiles do |t|
|
33
|
+
# t.integer :id
|
34
|
+
t.string :name
|
35
|
+
t.integer :gender_id
|
36
|
+
end
|
37
|
+
class Profile < ActiveRecord::Base
|
38
|
+
belongs_to :gender
|
39
|
+
end
|
40
|
+
Profile.delete_all
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
let(:him) { Profile.create(:name => 'Him', :gender => Gender::MALE) }
|
45
|
+
let(:her) { Profile.create(:name => 'Her', :gender => Gender::FEMALE) }
|
46
|
+
|
47
|
+
|
48
|
+
context "should use cached when going over association" do
|
49
|
+
|
50
|
+
it 'should find the cached ones (no :include)' do
|
51
|
+
him
|
52
|
+
him.reload #to empty the assoc cache
|
53
|
+
|
54
|
+
Gender.connection.should_not_receive(:exec_query)
|
55
|
+
him.gender.name.should == 'male'
|
56
|
+
end
|
57
|
+
|
58
|
+
=begin
|
59
|
+
|
60
|
+
does not work: Profile.includes(:gender).all creates two sql statements
|
61
|
+
to load profiles AND genders associated to them. The 2nd one (for genders)
|
62
|
+
does not run through standard finders but is done in the depth of AR.
|
63
|
+
So caching does not work here.
|
64
|
+
|
65
|
+
Possible improvements:
|
66
|
+
* intercept `all' for ALL AR models and take out inclusions where the
|
67
|
+
model is cached (restricted to simple cases of belongs_to)
|
68
|
+
* find an entry point deeper in AR where associations are loaded and
|
69
|
+
modify that to consider model caching
|
70
|
+
|
71
|
+
Until then, one should just leave out cached models in inclusions,
|
72
|
+
though that makes switching caching on or off for a model quite difficult.
|
73
|
+
|
74
|
+
it "should take the :include from the cache" do
|
75
|
+
#logged do
|
76
|
+
him;her
|
77
|
+
ActiveRecord::Base.connection.should_receive(:exec_query).once.and_call_original
|
78
|
+
all=Profile.includes(:gender).all
|
79
|
+
ActiveRecord::Base.connection.should_not_receive(:exec_query)
|
80
|
+
him.gender.name.should == 'male'
|
81
|
+
her.gender.name.should == 'female'
|
82
|
+
#end
|
83
|
+
end
|
84
|
+
=end
|
85
|
+
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe 'simple caching' do
|
5
|
+
before :all do
|
6
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
7
|
+
ActiveRecord::Migration.create_table :models do |t|
|
8
|
+
# t.integer :id
|
9
|
+
t.string :name
|
10
|
+
t.string :other
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
before do
|
16
|
+
@klass=Class.new(ActiveRecord::Base)
|
17
|
+
@klass.table_name="models"
|
18
|
+
@klass.delete_all
|
19
|
+
@klass.create(:name => 'one', :other => 'eins')
|
20
|
+
@klass.create(:name => 'two', :other => 'zwei')
|
21
|
+
@klass.create(:name => 'three', :other => 'drei')
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:one) { @klass.find_by(:name => "one") }
|
25
|
+
let(:three) { @klass.find_by(:name => "three") }
|
26
|
+
let(:two) { @klass.find_by(:name => "two") }
|
27
|
+
|
28
|
+
context "cache_enumeration?" do
|
29
|
+
it "should return the rigth value" do
|
30
|
+
@klass.should_not be_cache_enumeration
|
31
|
+
@klass.cache_enumeration.cache!
|
32
|
+
@klass.should be_cache_enumeration
|
33
|
+
end
|
34
|
+
it "should not cache if not activated" do
|
35
|
+
@klass.all
|
36
|
+
@klass.should_not be_cache_enumeration
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
context "all" do
|
42
|
+
before do
|
43
|
+
@klass.cache_enumeration(:constantize => false)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should fire db queries if all is modified" do
|
47
|
+
@klass.cache_enumeration.cache!
|
48
|
+
@klass.connection.should_receive(:exec_query).and_call_original
|
49
|
+
@klass.where("name in ('one','two')").all.size.should == 2
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should fire db queries if all has parameters' do
|
53
|
+
@klass.cache_enumeration.cache!
|
54
|
+
#@klass.connection.should_receive(:exec_query)..and_call_original
|
55
|
+
@klass.where("name = 'one'").all.size.should == 1
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should fire db queries if all with parameters is used through find(:all)' do
|
59
|
+
@klass.cache_enumeration.cache!
|
60
|
+
@klass.connection.should_receive(:exec_query).and_call_original
|
61
|
+
@klass.where("name = 'one'").all.size.should == 1
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should fire db queries if all with select parameter is used through find(:all)' do
|
65
|
+
@klass.cache_enumeration.cache!
|
66
|
+
@klass.connection.should_receive(:exec_query).and_call_original
|
67
|
+
entry = @klass.select("id, name").all.first
|
68
|
+
lambda {
|
69
|
+
entry.other
|
70
|
+
}.should raise_error(ActiveModel::MissingAttributeError, 'missing attribute: other')
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'first' do
|
76
|
+
it 'should find the first entry' do
|
77
|
+
@klass.cache_enumeration.cache!
|
78
|
+
@klass.first.should == one
|
79
|
+
end
|
80
|
+
it 'should allwo string order (and use cache)' do
|
81
|
+
@klass.cache_enumeration(:order => "other").cache!
|
82
|
+
@klass.connection.should_not_receive(:exec_query)
|
83
|
+
|
84
|
+
@klass.order('other').first.should == three
|
85
|
+
end
|
86
|
+
it 'should allow hash condition (and use cache)' do
|
87
|
+
@klass.cache_enumeration.cache!
|
88
|
+
@klass.connection.should_not_receive(:exec_query)
|
89
|
+
|
90
|
+
@klass.where(:name => 'three').first.should == three
|
91
|
+
end
|
92
|
+
it 'should allow string conditions (and ask db)' do
|
93
|
+
@klass.cache_enumeration.cache!
|
94
|
+
@klass.connection.should_receive(:exec_query).and_call_original
|
95
|
+
@klass.where("other = 'drei'").first.should == three
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should allow hash conditions in first (and use cache)' do
|
99
|
+
@klass.cache_enumeration.cache!
|
100
|
+
@klass.connection.should_not_receive(:exec_query)
|
101
|
+
@klass.where(:name => 'three' ).first.should == three
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should allow conditions for first (and use db)' do
|
105
|
+
@klass.cache_enumeration.cache!
|
106
|
+
@klass.connection.should_receive(:exec_query).and_call_original
|
107
|
+
@klass.where("other = 'drei'").first.should == three
|
108
|
+
end
|
109
|
+
it 'should allow conditions for first (and use db)' do
|
110
|
+
@klass.cache_enumeration.cache!
|
111
|
+
@klass.connection.should_receive(:exec_query).and_call_original
|
112
|
+
expect(@klass.where("name = 'three'").first).not_to be_nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "finders" do
|
117
|
+
before do
|
118
|
+
@klass.cache_enumeration(:constantize => false)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should find objects providing id' do
|
122
|
+
@klass.cache_enumeration.cache!
|
123
|
+
@klass.connection.should_not_receive(:exec_query)
|
124
|
+
|
125
|
+
@klass.find(one.id).id.should == one.id
|
126
|
+
@klass.find(one.id).frozen?().should eq(true)
|
127
|
+
@klass.find([one.id])[0].id.should == one.id
|
128
|
+
@klass.find([]).size.should == 0
|
129
|
+
|
130
|
+
lambda { @klass.find(0) }.should raise_error(ActiveRecord::RecordNotFound)
|
131
|
+
lambda { @klass.find(nil) }.should raise_error(ActiveRecord::RecordNotFound)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should find an array of object ids (and hit cache)" do
|
135
|
+
@klass.cache_enumeration.cache!
|
136
|
+
@klass.connection.should_not_receive(:exec_query)
|
137
|
+
|
138
|
+
@klass.find([one.id, three.id]).collect { |item| item.id }.should == [one.id, three.id]
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should find_by' do
|
142
|
+
@klass.cache_enumeration.cache!
|
143
|
+
@klass.connection.should_not_receive(:exec_query)
|
144
|
+
|
145
|
+
@klass.find_by(:id => one.id).id.should == one.id
|
146
|
+
@klass.find_by(:id => one.id.to_s).id.should == one.id
|
147
|
+
@klass.find_by(:id => 0).should be_nil
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should find objects by_name' do
|
151
|
+
one
|
152
|
+
@klass.cache_enumeration.cache!
|
153
|
+
@klass.connection.should_not_receive(:exec_query)
|
154
|
+
|
155
|
+
@klass.find_by(:name => 'one').id.should == one.id
|
156
|
+
@klass.find_by(:name => 'no such name').should be_nil
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
context "multiple keys" do
|
162
|
+
it 'it should store by multiple keys (hashing)' do
|
163
|
+
@klass.cache_enumeration(:hashed => ['id', 'other', 'name']).cache!
|
164
|
+
@klass.connection.should_not_receive(:exec_query)
|
165
|
+
@klass.find_by(:other => 'eins').id.should ==one.id
|
166
|
+
@klass.find_by(:name => 'one').id.should ==one.id
|
167
|
+
end
|
168
|
+
end
|
169
|
+
context "sorting of all" do
|
170
|
+
it 'should sort by option' do
|
171
|
+
@klass.cache_enumeration(:order => 'name').cache!
|
172
|
+
@klass.connection.should_not_receive(:exec_query)
|
173
|
+
|
174
|
+
@klass.order("name").all.collect { |item| item.id }.should == [one.id, three.id, two.id]
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
context "constantize" do
|
179
|
+
it "should constantize name by default" do
|
180
|
+
@klass.cache_enumeration.options[:constantize].should == 'name'
|
181
|
+
end
|
182
|
+
it "should without no preloading" do
|
183
|
+
|
184
|
+
@klass.cache_enumeration
|
185
|
+
@klass::ONE.id.should == one.id
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'should constantize other fields' do
|
189
|
+
@klass.cache_enumeration(:constantize => 'other').cache!
|
190
|
+
@klass.cache_enumeration.options[:constantize].should == 'other'
|
191
|
+
@klass.connection.should_not_receive(:exec_query)
|
192
|
+
|
193
|
+
@klass::EINS.id.should == one.id
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should contantize by lambda" do
|
197
|
+
@klass.cache_enumeration(:constantize => lambda { |model| model.other }).cache!
|
198
|
+
@klass.connection.should_not_receive(:exec_query)
|
199
|
+
|
200
|
+
@klass::EINS.id.should == one.id
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
lib = File.expand_path('../lib', __FILE__)
|
9
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
10
|
+
|
11
|
+
require 'cached_enumeration'
|
12
|
+
#require 'byebug'
|
13
|
+
#require 'rubinius/debugger'
|
14
|
+
require 'logger'
|
15
|
+
#ActiveRecord::Base.logger=Logger.new(STDOUT)
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
18
|
+
config.run_all_when_everything_filtered = true
|
19
|
+
config.filter_run :focus
|
20
|
+
|
21
|
+
# Run specs in random order to surface order dependencies. If you find an
|
22
|
+
# order dependency and want to debug it, you can fix the order by providing
|
23
|
+
# the seed, which is printed after each run.
|
24
|
+
# --seed 1234
|
25
|
+
config.order = 'random'
|
26
|
+
end
|
27
|
+
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cached_enumeration
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Schrammel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2014-10-23 00:00:00 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
prerelease: false
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: "1.3"
|
22
|
+
type: :development
|
23
|
+
version_requirements: *id001
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: rake
|
26
|
+
prerelease: false
|
27
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- &id006
|
30
|
+
- ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id002
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: "3.1"
|
43
|
+
type: :development
|
44
|
+
version_requirements: *id003
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: sqlite3
|
47
|
+
prerelease: false
|
48
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - "="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.3.8
|
53
|
+
type: :development
|
54
|
+
version_requirements: *id004
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord
|
57
|
+
prerelease: false
|
58
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "4.1"
|
63
|
+
type: :runtime
|
64
|
+
version_requirements: *id005
|
65
|
+
description: Cache nonchanging ActiveRecord models in memory
|
66
|
+
email:
|
67
|
+
- peter.schrammel@experteer.com
|
68
|
+
executables: []
|
69
|
+
|
70
|
+
extensions: []
|
71
|
+
|
72
|
+
extra_rdoc_files: []
|
73
|
+
|
74
|
+
files:
|
75
|
+
- .gitignore
|
76
|
+
- .travis.yml
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE.txt
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- cached_enumeration.gemspec
|
82
|
+
- lib/cached_enumeration.rb
|
83
|
+
- lib/cached_enumeration/cached_enumeration.rb
|
84
|
+
- lib/cached_enumeration/version.rb
|
85
|
+
- spec/lib/association_spec.rb
|
86
|
+
- spec/lib/cached_enumeration_spec.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
homepage: ""
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- *id006
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- *id006
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.2.2
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: Cache nonchanging ActiveRecord models in memory
|
111
|
+
test_files:
|
112
|
+
- spec/lib/association_spec.rb
|
113
|
+
- spec/lib/cached_enumeration_spec.rb
|
114
|
+
- spec/spec_helper.rb
|
115
|
+
has_rdoc:
|