Class: Clevic::CacheTable
- Includes:
- OrderedDataset
- Defined in:
- lib/clevic/cache_table.rb
Overview
Fetch rows from the db on demand, rather than all up front.
Being able to change the recordset on the fly and still find a previously known entity in the set requires a defined ordering, so if no ordering is specified, the primary key of the entity will be used.
It hasn’t been tested with compound primary keys.
#–
TODO drop rows when they haven’t been accessed for a while
TODO how to handle a quickly-changing underlying table? invalidate cache for each call?
TODO figure out how to handle situations where the character set ordering in the db and in Ruby are different.
Instance Attribute Summary collapse
-
#entity_class ⇒ Object
readonly
Returns the value of attribute entity_class.
-
#preload_count ⇒ Object
the number of records loaded in one call to the db.
Attributes included from OrderedDataset
Instance Method Summary collapse
-
#[](index) ⇒ Object
return the entity at the given index.
-
#compare(key, candidate, direction) ⇒ Object
key is what we’re searching for.
-
#fetch_entity(index) ⇒ Object
Fetch the entity for the given index from the db, and store it in the array.
-
#index_for_entity(entity) ⇒ Object
find the index for the given entity, using a binary search algorithm (bsearch).
-
#initialize(entity_class, dataset = nil) ⇒ CacheTable
constructor
A new instance of CacheTable.
-
#preload_limit(limit, &block) ⇒ Object
Execute the block with the specified preload_count, and restore the existing one when done.
-
#renew(new_dataset = nil, &block) ⇒ Object
Make a new instance based on the current dataset.
-
#sql_count ⇒ Object
The count of the records according to the db, which may be different to the records in the cache.
Methods included from OrderedDataset
Methods inherited from Array
#cached_at?, #group, #range, #search, #section, #sparse, #sparse_hash
Constructor Details
#initialize(entity_class, dataset = nil) ⇒ CacheTable
Returns a new instance of CacheTable.
34 35 36 37 38 39 40 41 42 43 |
# File 'lib/clevic/cache_table.rb', line 34 def initialize( entity_class, dataset = nil ) @preload_count = 30 @entity_class = entity_class # defined in OrderAttributes self.dataset = dataset || entity_class.dataset # size the array and fill it with nils. They'll be filled # in by the [] operator super( sql_count ) end |
Instance Attribute Details
#entity_class ⇒ Object (readonly)
Returns the value of attribute entity_class.
32 33 34 |
# File 'lib/clevic/cache_table.rb', line 32 def entity_class @entity_class end |
#preload_count ⇒ Object
the number of records loaded in one call to the db
31 32 33 |
# File 'lib/clevic/cache_table.rb', line 31 def preload_count @preload_count end |
Instance Method Details
#[](index) ⇒ Object
return the entity at the given index. Fetch it from the db if it isn’t in this array yet
79 80 81 |
# File 'lib/clevic/cache_table.rb', line 79 def []( index ) super( index ) || fetch_entity( index ) end |
#compare(key, candidate, direction) ⇒ Object
key is what we’re searching for. candidate is what the current candidate is. direction is 1 for sorted ascending, and -1 for sorted descending TODO retrieve nulls first/last from dataset. In sequel (>3.13.0) this is related to entity_class.filter( :release_date.desc(:nulls=>:first), :name.asc(:nulls=>:last) )
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/clevic/cache_table.rb', line 109 def compare( key, candidate, direction ) if ( key_nil = key.nil? ) || candidate.nil? if key == candidate # both nil, ie equal 0 else # assume nil is sorted greater # TODO this should be retrieved from the db # ie candidate(nil) <=> key is 1 # and key <=> candidate(nil) is -1 key_nil ? -1 : 1 end else candidate <=> key end * direction # reverse the result if we're searching a desc attribute, # where direction will be -1 end |
#fetch_entity(index) ⇒ Object
Fetch the entity for the given index from the db, and store it in the array. Also, preload preload_count records to avoid subsequent hits on the db
65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/clevic/cache_table.rb', line 65 def fetch_entity( index ) # calculate negative indices for the SQL offset offset = index < 0 ? index + sql_count : index # fetch self.preload_count records records = dataset.limit( preload_count, offset ) records.each_with_index {|x,i| self[i+index] = x if !cached_at?( i+index )} # return the first one records.first end |
#index_for_entity(entity) ⇒ Object
find the index for the given entity, using a binary search algorithm (bsearch). The order_by ActiveRecord style options are used to do the binary search. nil is returned if the entity is nil nil is returned if the array is empty
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/clevic/cache_table.rb', line 132 def index_for_entity( entity ) return nil if size == 0 || entity.nil? # only load one record at a time, because mostly we only # need one for the binary seach. No point in pulling several out. preload_limit( 1 ) do # do the binary search based on what we know about the search order bsearch do |candidate| # find using all sort attributes order_attributes.inject(0) do |result,attribute| # result from the block should be in [-1,0,1], # similar to candidate <=> entity key, direction = attribute if result == 0 return nil unless entity.respond_to? key return nil unless candidate.respond_to? key # compare taking ordering direction into account retval = compare( entity.send( key ), candidate.send( key ), direction ) # exit now because we have a difference next( retval ) if retval != 0 # otherwise try with the next order attribute retval else # recurse out because we have a difference already result end end end end end |
#preload_limit(limit, &block) ⇒ Object
Execute the block with the specified preload_count, and restore the existing one when done. Return the value of the block
54 55 56 57 58 59 60 |
# File 'lib/clevic/cache_table.rb', line 54 def preload_limit( limit, &block ) old_limit = preload_count self.preload_count = limit retval = yield self.preload_count = old_limit retval end |
#renew(new_dataset = nil, &block) ⇒ Object
Make a new instance based on the current dataset. Unless new_dataset is specified, pass the dataset to the block, and use the return value from the block as the new dataset.
This is so that filter of datasets can be based on the existing one, but it’s easy to go back to previous data sets if necessary. TODO write tests for both cases.
92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/clevic/cache_table.rb', line 92 def renew( new_dataset = nil, &block ) if new_dataset && block_given? raise "Passing a new dataset and a modification block doesn't make sense." end if block_given? self.class.new( entity_class, block.call( dataset ) ) else self.class.new( entity_class, new_dataset || dataset ) end end |
#sql_count ⇒ Object
The count of the records according to the db, which may be different to the records in the cache
47 48 49 |
# File 'lib/clevic/cache_table.rb', line 47 def sql_count dataset.count end |