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 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: