cached_enumeration 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ script: "bundle exec rake spec"
3
+ rvm:
4
+ - 2.0.0
5
+ - rbx-2.2.10
6
+ - 2.1.3
7
+ notifications:
8
+ recipients:
9
+ - peter.schrammel@experteer.com
10
+ branches:
11
+ only:
12
+ - master
13
+ - rails32
14
+
15
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cached_enumeration.gemspec
4
+ gemspec
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,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+
5
+ RSpec::Core::RakeTask.new do |t|
6
+
7
+ end
@@ -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,4 @@
1
+ require 'active_record'
2
+ require "cached_enumeration/version"
3
+ require "cached_enumeration/cached_enumeration"
4
+
@@ -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,3 @@
1
+ module CachedEnumeration
2
+ VERSION = "2.0.0"
3
+ 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
@@ -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: