record-cache 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/record_cache.rb +1 -1
- data/lib/record_cache/query.rb +0 -1
- data/lib/record_cache/strategy/base.rb +2 -86
- data/lib/record_cache/strategy/id_cache.rb +3 -3
- data/lib/record_cache/strategy/util.rb +114 -0
- data/lib/record_cache/version.rb +1 -1
- data/lib/record_cache/version_store.rb +2 -2
- data/spec/lib/strategy/base_spec.rb +0 -8
- data/spec/lib/strategy/util_spec.rb +228 -0
- metadata +10 -7
data/lib/record_cache.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Record Cache files
|
2
2
|
["query", "version_store", "multi_read",
|
3
|
-
"strategy/base", "strategy/id_cache", "strategy/index_cache", "strategy/request_cache",
|
3
|
+
"strategy/util", "strategy/base", "strategy/id_cache", "strategy/index_cache", "strategy/request_cache",
|
4
4
|
"statistics", "dispatcher", "base"].each do |file|
|
5
5
|
require File.dirname(__FILE__) + "/record_cache/#{file}.rb"
|
6
6
|
end
|
data/lib/record_cache/query.rb
CHANGED
@@ -12,7 +12,6 @@ module RecordCache
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Set equality of an attribute (usually found in where clause)
|
15
|
-
# Returns false if another attribute values was already set (making this query uncachable)
|
16
15
|
def where(attribute, values)
|
17
16
|
@wheres[attribute.to_sym] = values if attribute
|
18
17
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module RecordCache
|
2
2
|
module Strategy
|
3
3
|
class Base
|
4
|
-
CLASS_KEY = :c
|
5
|
-
ATTRIBUTES_KEY = :a
|
6
4
|
|
7
5
|
def initialize(base, strategy_id, record_store, options)
|
8
6
|
@base = base
|
@@ -14,8 +12,8 @@ module RecordCache
|
|
14
12
|
# Fetch all records and sort and filter locally
|
15
13
|
def fetch(query)
|
16
14
|
records = fetch_records(query)
|
17
|
-
filter!(records, query.wheres) if query.wheres.size > 0
|
18
|
-
sort!(records, query.sort_orders) if query.sorted?
|
15
|
+
Util.filter!(records, query.wheres) if query.wheres.size > 0
|
16
|
+
Util.sort!(records, query.sort_orders) if query.sorted?
|
19
17
|
records
|
20
18
|
end
|
21
19
|
|
@@ -67,88 +65,6 @@ module RecordCache
|
|
67
65
|
"#{cache_key}v#{version.to_s}".freeze
|
68
66
|
end
|
69
67
|
|
70
|
-
# serialize one record before adding it to the cache
|
71
|
-
# creates a shallow clone with a version and without associations
|
72
|
-
def serialize(record)
|
73
|
-
{CLASS_KEY => record.class.name,
|
74
|
-
ATTRIBUTES_KEY => record.instance_variable_get(:@attributes)}.freeze
|
75
|
-
end
|
76
|
-
|
77
|
-
# deserialize a cached record
|
78
|
-
def deserialize(serialized)
|
79
|
-
record = serialized[CLASS_KEY].constantize.new
|
80
|
-
attributes = serialized[ATTRIBUTES_KEY]
|
81
|
-
record.instance_variable_set(:@attributes, Hash[attributes])
|
82
|
-
record.instance_variable_set(:@new_record, false)
|
83
|
-
record.instance_variable_set(:@changed_attributes, {})
|
84
|
-
record.instance_variable_set(:@previously_changed, {})
|
85
|
-
record
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
# Filter the cached records in memory
|
91
|
-
# only simple x = y or x IN (a,b,c) can be handled
|
92
|
-
def filter!(records, wheres)
|
93
|
-
wheres.each_pair do |attr, value|
|
94
|
-
if value.is_a?(Array)
|
95
|
-
records.reject! { |record| !value.include?(record.send(attr)) }
|
96
|
-
else
|
97
|
-
records.reject! { |record| record.send(attr) != value }
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Sort the cached records in memory
|
103
|
-
def sort!(records, sort_orders)
|
104
|
-
records.sort!(&sort_proc(sort_orders))
|
105
|
-
Collator.clear
|
106
|
-
records
|
107
|
-
end
|
108
|
-
|
109
|
-
# Retrieve the Proc based on the order by attributes
|
110
|
-
# Note: Case insensitive sorting with collation is used for Strings
|
111
|
-
def sort_proc(sort_orders)
|
112
|
-
# [['(COLLATER.collate(x.name) || NIL_COMES_FIRST)', 'COLLATER.collate(y.name)'], ['(y.updated_at || NIL_COMES_FIRST)', 'x.updated_at']]
|
113
|
-
sort = sort_orders.map do |attr, asc|
|
114
|
-
lr = ["x.", "y."]
|
115
|
-
lr.reverse! unless asc
|
116
|
-
lr.each{ |s| s << attr }
|
117
|
-
lr.each{ |s| s.replace("Collator.collate(#{s})") } if @base.columns_hash[attr].type == :string
|
118
|
-
lr[0].replace("(#{lr[0]} || NIL_COMES_FIRST)")
|
119
|
-
lr
|
120
|
-
end
|
121
|
-
# ['[(COLLATER.collate(x.name) || NIL_COMES_FIRST), (y.updated_at || NIL_COMES_FIRST)]', '[COLLATER.collate(y.name), x.updated_at]']
|
122
|
-
sort = sort.transpose.map{|s| s.size == 1 ? s.first : "[#{s.join(',')}]"}
|
123
|
-
# Proc.new{ |x,y| { ([(COLLATER.collate(x.name) || NIL_COMES_FIRST), (y.updated_at || NIL_COMES_FIRST)] <=> [COLLATER.collate(y.name), x.updated_at]) || 1 }
|
124
|
-
eval("Proc.new{ |x,y| (#{sort[0]} <=> #{sort[1]}) || 1 }")
|
125
|
-
end
|
126
|
-
|
127
|
-
# If +x.nil?+ this class will return -1 for +x <=> y+
|
128
|
-
NIL_COMES_FIRST = ((class NilComesFirst; def <=>(y); -1; end; end); NilComesFirst.new)
|
129
|
-
|
130
|
-
# StringCollator uses the Rails transliterate method for collation
|
131
|
-
module Collator
|
132
|
-
@collated = []
|
133
|
-
|
134
|
-
def self.clear
|
135
|
-
@collated.each { |string| string.send(:remove_instance_variable, :@rc_collated) }
|
136
|
-
@collated.clear
|
137
|
-
end
|
138
|
-
|
139
|
-
def self.collate(string)
|
140
|
-
collated = string.instance_variable_get(:@rc_collated)
|
141
|
-
return collated if collated
|
142
|
-
normalized = ActiveSupport::Multibyte::Unicode.normalize(ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c).mb_chars
|
143
|
-
collated = I18n.transliterate(normalized).downcase.mb_chars
|
144
|
-
# transliterate will replace ignored/unknown chars with ? the following line replaces ? with the original character
|
145
|
-
collated.chars.each_with_index{ |c, i| collated[i] = normalized[i] if c == '?' } if collated.index('?')
|
146
|
-
# puts "collation: #{string} => #{collated.to_s}"
|
147
|
-
string.instance_variable_set(:@rc_collated, collated)
|
148
|
-
@collated << string
|
149
|
-
collated
|
150
|
-
end
|
151
|
-
end
|
152
68
|
end
|
153
69
|
end
|
154
70
|
end
|
@@ -16,7 +16,7 @@ module RecordCache
|
|
16
16
|
else
|
17
17
|
# update the version store and add the record to the cache
|
18
18
|
new_version = version_store.increment(key)
|
19
|
-
record_store.write(versioned_key(key, new_version), serialize(record))
|
19
|
+
record_store.write(versioned_key(key, new_version), Util.serialize(record))
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -56,7 +56,7 @@ module RecordCache
|
|
56
56
|
# retrieve the records from the cache with the given keys
|
57
57
|
def from_cache(id_to_versioned_key_map)
|
58
58
|
records = record_store.read_multi(*(id_to_versioned_key_map.values)).values.compact
|
59
|
-
records.map{ |record| deserialize(record) }
|
59
|
+
records.map{ |record| Util.deserialize(record) }
|
60
60
|
end
|
61
61
|
|
62
62
|
# retrieve the records with the given ids from the database
|
@@ -72,7 +72,7 @@ module RecordCache
|
|
72
72
|
versioned_key = versioned_key(key, version_store.renew(key))
|
73
73
|
end
|
74
74
|
# store the record based on the versioned key
|
75
|
-
record_store.write(versioned_key, serialize(record))
|
75
|
+
record_store.write(versioned_key, Util.serialize(record))
|
76
76
|
end
|
77
77
|
records
|
78
78
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Utility methods for the Cache Strategies
|
2
|
+
module RecordCache
|
3
|
+
module Strategy
|
4
|
+
module Util
|
5
|
+
CLASS_KEY = :c
|
6
|
+
ATTRIBUTES_KEY = :a
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# serialize one record before adding it to the cache
|
11
|
+
# creates a shallow clone with a version and without associations
|
12
|
+
def serialize(record)
|
13
|
+
{CLASS_KEY => record.class.name,
|
14
|
+
ATTRIBUTES_KEY => record.instance_variable_get(:@attributes)}.freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
# deserialize a cached record
|
18
|
+
def deserialize(serialized)
|
19
|
+
record = serialized[CLASS_KEY].constantize.new
|
20
|
+
attributes = serialized[ATTRIBUTES_KEY]
|
21
|
+
record.instance_variable_set(:@attributes, Hash[attributes])
|
22
|
+
record.instance_variable_set(:@new_record, false)
|
23
|
+
record.instance_variable_set(:@changed_attributes, {})
|
24
|
+
record.instance_variable_set(:@previously_changed, {})
|
25
|
+
record
|
26
|
+
end
|
27
|
+
|
28
|
+
# Filter the cached records in memory
|
29
|
+
# only simple x = y or x IN (a,b,c) can be handled
|
30
|
+
# Example:
|
31
|
+
# RecordCache::Strategy::Util.filter!(Apple.all, :price => [0.49, 0.59, 0.69], :name => "Green Apple")
|
32
|
+
def filter!(records, wheres)
|
33
|
+
wheres.each_pair do |attr, value|
|
34
|
+
attr = attr.to_sym
|
35
|
+
if value.is_a?(Array)
|
36
|
+
records.reject! { |record| !value.include?(record.send(attr)) }
|
37
|
+
else
|
38
|
+
records.reject! { |record| record.send(attr) != value }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Sort the cached records in memory, similar to MySql sorting rules including collatiom
|
44
|
+
# Simply provide the Symbols of the attributes to sort in Ascending order, or use
|
45
|
+
# [<attribute>, false] for Descending order.
|
46
|
+
# Example:
|
47
|
+
# RecordCache::Strategy::Util.sort!(Apple.all, :name)
|
48
|
+
# RecordCache::Strategy::Util.sort!(Apple.all, [:name, false])
|
49
|
+
# RecordCache::Strategy::Util.sort!(Apple.all, [:price, false], :name)
|
50
|
+
# RecordCache::Strategy::Util.sort!(Apple.all, [:price, false], [:name, true])
|
51
|
+
# RecordCache::Strategy::Util.sort!(Apple.all, [[:price, false], [:name, true]])
|
52
|
+
def sort!(records, *sort_orders)
|
53
|
+
return records if records.empty? || sort_orders.empty?
|
54
|
+
if sort_orders.first.is_a?(Array) && sort_orders.first.first.is_a?(Array)
|
55
|
+
sort_orders = sort_orders.first
|
56
|
+
else
|
57
|
+
sort_orders = sort_orders.map{ |order| order.is_a?(Array) ? order : [order, true] } unless sort_orders.all?{ |order| order.is_a?(Array) }
|
58
|
+
end
|
59
|
+
records.sort!(&sort_proc(records.first.class, sort_orders))
|
60
|
+
Collator.clear
|
61
|
+
records
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Retrieve the Proc based on the order by attributes
|
67
|
+
# Note: Case insensitive sorting with collation is used for Strings
|
68
|
+
def sort_proc(base, sort_orders)
|
69
|
+
# [['(COLLATER.collate(x.name) || NIL_COMES_FIRST)', 'COLLATER.collate(y.name)'], ['(y.updated_at || NIL_COMES_FIRST)', 'x.updated_at']]
|
70
|
+
sort = sort_orders.map do |attr, asc|
|
71
|
+
attr = attr.to_s
|
72
|
+
lr = ["x.", "y."]
|
73
|
+
lr.reverse! unless asc
|
74
|
+
lr.each{ |s| s << attr }
|
75
|
+
lr.each{ |s| s.replace("Collator.collate(#{s})") } if base.columns_hash[attr].type == :string
|
76
|
+
lr[0].replace("(#{lr[0]} || NIL_COMES_FIRST)")
|
77
|
+
lr
|
78
|
+
end
|
79
|
+
# ['[(COLLATER.collate(x.name) || NIL_COMES_FIRST), (y.updated_at || NIL_COMES_FIRST)]', '[COLLATER.collate(y.name), x.updated_at]']
|
80
|
+
sort = sort.transpose.map{|s| s.size == 1 ? s.first : "[#{s.join(',')}]"}
|
81
|
+
# Proc.new{ |x,y| { ([(COLLATER.collate(x.name) || NIL_COMES_FIRST), (y.updated_at || NIL_COMES_FIRST)] <=> [COLLATER.collate(y.name), x.updated_at]) || 1 }
|
82
|
+
eval("Proc.new{ |x,y| (#{sort[0]} <=> #{sort[1]}) || 1 }")
|
83
|
+
end
|
84
|
+
|
85
|
+
# If +x.nil?+ this class will return -1 for +x <=> y+
|
86
|
+
NIL_COMES_FIRST = ((class NilComesFirst; def <=>(y); -1; end; end); NilComesFirst.new)
|
87
|
+
|
88
|
+
# StringCollator uses the Rails transliterate method for collation
|
89
|
+
module Collator
|
90
|
+
@collated = []
|
91
|
+
|
92
|
+
def self.clear
|
93
|
+
@collated.each { |string| string.send(:remove_instance_variable, :@rc_collated) }
|
94
|
+
@collated.clear
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.collate(string)
|
98
|
+
collated = string.instance_variable_get(:@rc_collated)
|
99
|
+
return collated if collated
|
100
|
+
normalized = ActiveSupport::Multibyte::Unicode.normalize(ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c).mb_chars
|
101
|
+
collated = I18n.transliterate(normalized).downcase.mb_chars
|
102
|
+
# transliterate will replace ignored/unknown chars with ? the following line replaces ? with the original character
|
103
|
+
collated.chars.each_with_index{ |c, i| collated[i] = normalized[i] if c == '?' } if collated.index('?')
|
104
|
+
# puts "collation: #{string} => #{collated.to_s}"
|
105
|
+
string.instance_variable_set(:@rc_collated, collated)
|
106
|
+
@collated << string
|
107
|
+
collated
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/record_cache/version.rb
CHANGED
@@ -23,7 +23,7 @@ module RecordCache
|
|
23
23
|
end
|
24
24
|
|
25
25
|
# In case the version store did not have a key anymore, call this methods
|
26
|
-
# to reset the key with a unique new
|
26
|
+
# to reset the key with a (unique) new version
|
27
27
|
def renew(key)
|
28
28
|
new_version = (Time.current.to_f * 10000).to_i
|
29
29
|
@store.write(key, new_version)
|
@@ -43,7 +43,7 @@ module RecordCache
|
|
43
43
|
version
|
44
44
|
end
|
45
45
|
|
46
|
-
# Delete key from the version store
|
46
|
+
# Delete key from the version store (records cached in the Record Store belonging to this key will become unreachable)
|
47
47
|
def delete(key)
|
48
48
|
deleted = @store.delete(key)
|
49
49
|
RecordCache::Base.logger.debug("Version Store: deleted #{key}") if RecordCache::Base.logger.debug?
|
@@ -29,14 +29,6 @@ describe RecordCache::Strategy::Base do
|
|
29
29
|
Banana.record_cache[:id].send(:versioned_key, "rc/Banana/1", 2312423).should == "rc/Banana/1v2312423"
|
30
30
|
end
|
31
31
|
|
32
|
-
it "should serialize a record (currently Active Record only)" do
|
33
|
-
Banana.record_cache[:id].send(:serialize, Banana.find(1)).should == {:a=>{"name"=>"Blue Banana 1", "id"=>1, "store_id"=>2, "person_id"=>4}, :c=>"Banana"}
|
34
|
-
end
|
35
|
-
|
36
|
-
it "should deserialize a record (currently Active Record only)" do
|
37
|
-
Banana.record_cache[:id].send(:deserialize, {:a=>{"name"=>"Blue Banana 1", "id"=>1, "store_id"=>2, "person_id"=>4}, :c=>"Banana"}).should == Banana.find(1)
|
38
|
-
end
|
39
|
-
|
40
32
|
context "filter" do
|
41
33
|
it "should apply filter on :id cache hits" do
|
42
34
|
lambda{ @apples = Apple.where(:id => [1,2]).where(:name => "Adams Apple 1").all }.should use_cache(Apple).on(:id)
|
@@ -0,0 +1,228 @@
|
|
1
|
+
$KCODE = 'UTF8'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RecordCache::Strategy::Util do
|
5
|
+
|
6
|
+
it "should serialize a record (currently Active Record only)" do
|
7
|
+
subject.serialize(Banana.find(1)).should == {:a=>{"name"=>"Blue Banana 1", "id"=>1, "store_id"=>2, "person_id"=>4}, :c=>"Banana"}
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should deserialize a record (currently Active Record only)" do
|
11
|
+
subject.deserialize({:a=>{"name"=>"Blue Banana 1", "id"=>1, "store_id"=>2, "person_id"=>4}, :c=>"Banana"}).should == Banana.find(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "filter" do
|
15
|
+
it "should apply filter" do
|
16
|
+
apples = Apple.where(:id => [1,2]).all
|
17
|
+
subject.filter!(apples, :name => "Adams Apple 1")
|
18
|
+
apples.should == [Apple.find_by_name("Adams Apple 1")]
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return empty array when filter does not match any record" do
|
22
|
+
apples = Apple.where(:id => [1,2]).all
|
23
|
+
subject.filter!(apples, :name => "Adams Apple Pie")
|
24
|
+
apples.should be_empty
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should filter on text" do
|
28
|
+
apples = Apple.where(:id => [1,2]).all
|
29
|
+
subject.filter!(apples, :name => "Adams Apple 1")
|
30
|
+
apples.should == [Apple.find_by_name("Adams Apple 1")]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should filter on integers" do
|
34
|
+
apples = Apple.where(:id => [1,2,8,9]).all
|
35
|
+
subject.filter!(apples, :store_id => 2)
|
36
|
+
apples.map(&:id).sort.should == [8,9]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should filter on dates" do
|
40
|
+
people = Person.where(:id => [1,2,3]).all
|
41
|
+
subject.filter!(people, :birthday => Date.civil(1953,11,11))
|
42
|
+
people.size.should == 1
|
43
|
+
people.first.name.should == "Blue"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should filter on floats" do
|
47
|
+
people = Person.where(:id => [1,2,3]).all
|
48
|
+
subject.filter!(people, :height => 1.75)
|
49
|
+
people.size.should == 2
|
50
|
+
people.map(&:name).sort.should == ["Blue", "Cris"]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should filter on arrays" do
|
54
|
+
apples = Apple.where(:id => [1,2,8,9])
|
55
|
+
subject.filter!(apples, :store_id => [2, 4])
|
56
|
+
apples.map(&:id).sort.should == [8,9]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should filter on multiple fields" do
|
60
|
+
# make sure two apples exist with the same name
|
61
|
+
apple = Apple.find(8)
|
62
|
+
apple.name = Apple.find(9).name
|
63
|
+
apple.save!
|
64
|
+
|
65
|
+
apples = Apple.where(:id => [1,2,3,8,9,10]).all
|
66
|
+
subject.filter!(apples, :store_id => [2, 4], :name => apple.name)
|
67
|
+
apples.size.should == 2
|
68
|
+
apples.map(&:name).should == [apple.name, apple.name]
|
69
|
+
apples.map(&:id).sort.should == [8,9]
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context "sort" do
|
75
|
+
it "should accept a Symbol as a sort order" do
|
76
|
+
people = Person.where(:id => [1,2,3]).all
|
77
|
+
subject.sort!(people, :name)
|
78
|
+
people.map(&:name).should == ["Adam", "Blue", "Cris"]
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should accept a single Array as a sort order" do
|
82
|
+
people = Person.where(:id => [1,2,3]).all
|
83
|
+
subject.sort!(people, [:name, false])
|
84
|
+
people.map(&:name).should == ["Cris", "Blue", "Adam"]
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should accept multiple Symbols as a sort order" do
|
88
|
+
people = Person.where(:id => [2,3,4,5]).all
|
89
|
+
subject.sort!(people, :height, :id)
|
90
|
+
people.map(&:height).should == [1.69, 1.75, 1.75, 1.91]
|
91
|
+
people.map(&:id).should == [4, 2, 3, 5]
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should accept a mix of Symbols and Arrays as a sort order" do
|
95
|
+
people = Person.where(:id => [2,3,4,5]).all
|
96
|
+
subject.sort!(people, [:height, false], :id)
|
97
|
+
people.map(&:height).should == [1.91, 1.75, 1.75, 1.69]
|
98
|
+
people.map(&:id).should == [5, 2, 3, 4]
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should accept multiple Arrays as a sort order" do
|
102
|
+
people = Person.where(:id => [2,3,4,5]).all
|
103
|
+
subject.sort!(people, [:height, false], [:id, false])
|
104
|
+
people.map(&:height).should == [1.91, 1.75, 1.75, 1.69]
|
105
|
+
people.map(&:id).should == [5, 3, 2, 4]
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should accept an Array with Arrays as a sort order (default used by record cache)" do
|
109
|
+
people = Person.where(:id => [2,3,4,5]).all
|
110
|
+
subject.sort!(people, [[:height, false], [:id, false]])
|
111
|
+
people.map(&:height).should == [1.91, 1.75, 1.75, 1.69]
|
112
|
+
people.map(&:id).should == [5, 3, 2, 4]
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should order nil first for ASC" do
|
116
|
+
apples = Apple.where(:store_id => 1).all
|
117
|
+
subject.sort!(apples, [:person_id, true])
|
118
|
+
apples.map(&:person_id).should == [nil, nil, 4, 4, 5]
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should order nil last for DESC" do
|
122
|
+
apples = Apple.where(:store_id => 1).all
|
123
|
+
subject.sort!(apples, [:person_id, false])
|
124
|
+
apples.map(&:person_id).should == [5, 4, 4, nil, nil]
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should order ascending on text" do
|
128
|
+
people = Person.where(:id => [1,2,3,4]).all
|
129
|
+
subject.sort!(people, [:name, true])
|
130
|
+
people.map(&:name).should == ["Adam", "Blue", "Cris", "Fry"]
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should order descending on text" do
|
134
|
+
people = Person.where(:id => [1,2,3,4]).all
|
135
|
+
subject.sort!(people, [:name, false])
|
136
|
+
people.map(&:name).should == ["Fry", "Cris", "Blue", "Adam"]
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should order ascending on integers" do
|
140
|
+
people = Person.where(:id => [4,2,1,3]).all
|
141
|
+
subject.sort!(people, [:id, true])
|
142
|
+
people.map(&:id).should == [1,2,3,4]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should order descending on integers" do
|
146
|
+
people = Person.where(:id => [4,2,1,3]).all
|
147
|
+
subject.sort!(people, [:id, false])
|
148
|
+
people.map(&:id).should == [4,3,2,1]
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should order ascending on dates" do
|
152
|
+
people = Person.where(:id => [1,2,3,4]).all
|
153
|
+
subject.sort!(people, [:birthday, true])
|
154
|
+
people.map(&:birthday).should == [Date.civil(1953,11,11), Date.civil(1975,03,20), Date.civil(1975,03,20), Date.civil(1985,01,20)]
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should order descending on dates" do
|
158
|
+
people = Person.where(:id => [1,2,3,4]).all
|
159
|
+
subject.sort!(people, [:birthday, false])
|
160
|
+
people.map(&:birthday).should == [Date.civil(1985,01,20), Date.civil(1975,03,20), Date.civil(1975,03,20), Date.civil(1953,11,11)]
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should order ascending on float" do
|
164
|
+
people = Person.where(:id => [1,2,3,4]).all
|
165
|
+
subject.sort!(people, [:height, true])
|
166
|
+
people.map(&:height).should == [1.69, 1.75, 1.75, 1.83]
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should order descending on float" do
|
170
|
+
people = Person.where(:id => [1,2,3,4]).all
|
171
|
+
subject.sort!(people, [:height, false])
|
172
|
+
people.map(&:height).should == [1.83, 1.75, 1.75, 1.69]
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should order on multiple fields (ASC + ASC)" do
|
176
|
+
people = Person.where(:id => [2,3,4,5]).all
|
177
|
+
subject.sort!(people, [:height, true], [:id, true])
|
178
|
+
people.map(&:height).should == [1.69, 1.75, 1.75, 1.91]
|
179
|
+
people.map(&:id).should == [4, 2, 3, 5]
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should order on multiple fields (ASC + DESC)" do
|
183
|
+
people = Person.where(:id => [2,3,4,5]).all
|
184
|
+
subject.sort!(people, [:height, true], [:id, false])
|
185
|
+
people.map(&:height).should == [1.69, 1.75, 1.75, 1.91]
|
186
|
+
people.map(&:id).should == [4, 3, 2, 5]
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should order on multiple fields (DESC + ASC)" do
|
190
|
+
people = Person.where(:id => [2,3,4,5]).all
|
191
|
+
subject.sort!(people, [:height, false], [:id, true])
|
192
|
+
people.map(&:height).should == [1.91, 1.75, 1.75, 1.69]
|
193
|
+
people.map(&:id).should == [5, 2, 3, 4]
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should order on multiple fields (DESC + DESC)" do
|
197
|
+
people = Person.where(:id => [2,3,4,5]).all
|
198
|
+
subject.sort!(people, [:height, false], [:id, false])
|
199
|
+
people.map(&:height).should == [1.91, 1.75, 1.75, 1.69]
|
200
|
+
people.map(&:id).should == [5, 3, 2, 4]
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should use mysql style collation" do
|
204
|
+
ids = []
|
205
|
+
ids << Person.create!(:name => "ċedriĉ 3").id # latin other special
|
206
|
+
ids << Person.create!(:name => "a cedric").id # first in ascending order
|
207
|
+
ids << Person.create!(:name => "čedriĉ 4").id # latin another special
|
208
|
+
ids << Person.create!(:name => "ćedriĉ Last").id # latin special lowercase
|
209
|
+
ids << Person.create!(:name => "sedric 1").id # second to last latin in ascending order
|
210
|
+
ids << Person.create!(:name => "Cedric 2").id # ascii uppercase
|
211
|
+
ids << Person.create!(:name => "čedriĉ คฉ Almost last cedric").id # latin special, with non-latin
|
212
|
+
ids << Person.create!(:name => "Sedric 2").id # last latin in ascending order
|
213
|
+
ids << Person.create!(:name => "1 cedric").id # numbers before characters
|
214
|
+
ids << Person.create!(:name => "cedric 1").id # ascii lowercase
|
215
|
+
ids << Person.create!(:name => "คฉ Really last").id # non-latin characters last in ascending order
|
216
|
+
ids << Person.create!(:name => "čedriĉ ꜩ Last").id # latin special, with latin non-collateable
|
217
|
+
|
218
|
+
names_asc = ["1 cedric", "a cedric", "cedric 1", "Cedric 2", "ċedriĉ 3", "čedriĉ 4", "ćedriĉ Last", "čedriĉ คฉ Almost last cedric", "čedriĉ ꜩ Last", "sedric 1", "Sedric 2", "คฉ Really last"]
|
219
|
+
people = Person.where(:id => ids).all
|
220
|
+
subject.sort!(people, [:name, true])
|
221
|
+
people.map(&:name).should == names_asc
|
222
|
+
|
223
|
+
subject.sort!(people, [:name, false])
|
224
|
+
people.map(&:name).should == names_asc.reverse
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: record-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 2
|
10
|
+
version: 0.1.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Orslumen
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-10-
|
18
|
+
date: 2011-10-20 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: rails
|
@@ -23,7 +23,7 @@ dependencies:
|
|
23
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
24
|
none: false
|
25
25
|
requirements:
|
26
|
-
- - "
|
26
|
+
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
hash: 7
|
29
29
|
segments:
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
|
-
- - "
|
41
|
+
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
hash: 7
|
44
44
|
segments:
|
@@ -168,6 +168,7 @@ files:
|
|
168
168
|
- lib/record_cache/strategy/id_cache.rb
|
169
169
|
- lib/record_cache/strategy/index_cache.rb
|
170
170
|
- lib/record_cache/strategy/request_cache.rb
|
171
|
+
- lib/record_cache/strategy/util.rb
|
171
172
|
- lib/record_cache/test/resettable_version_store.rb
|
172
173
|
- lib/record_cache/version.rb
|
173
174
|
- lib/record_cache/version_store.rb
|
@@ -183,6 +184,7 @@ files:
|
|
183
184
|
- spec/lib/strategy/id_cache_spec.rb
|
184
185
|
- spec/lib/strategy/index_cache_spec.rb
|
185
186
|
- spec/lib/strategy/request_cache_spec.rb
|
187
|
+
- spec/lib/strategy/util_spec.rb
|
186
188
|
- spec/lib/version_store_spec.rb
|
187
189
|
- spec/models/apple.rb
|
188
190
|
- spec/models/banana.rb
|
@@ -226,7 +228,7 @@ rubyforge_project:
|
|
226
228
|
rubygems_version: 1.8.11
|
227
229
|
signing_key:
|
228
230
|
specification_version: 3
|
229
|
-
summary: Record Cache v0.1.
|
231
|
+
summary: Record Cache v0.1.2 transparantly stores Records in a Cache Store and retrieve those Records from the store when queried (by ID) using Active Model.
|
230
232
|
test_files:
|
231
233
|
- spec/db/database.yml
|
232
234
|
- spec/db/schema.rb
|
@@ -240,6 +242,7 @@ test_files:
|
|
240
242
|
- spec/lib/strategy/id_cache_spec.rb
|
241
243
|
- spec/lib/strategy/index_cache_spec.rb
|
242
244
|
- spec/lib/strategy/request_cache_spec.rb
|
245
|
+
- spec/lib/strategy/util_spec.rb
|
243
246
|
- spec/lib/version_store_spec.rb
|
244
247
|
- spec/models/apple.rb
|
245
248
|
- spec/models/banana.rb
|