record-cache 0.1.1 → 0.1.2
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.
- 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
|