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.
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module RecordCache # :nodoc:
2
2
  module Version # :nodoc:
3
- STRING = '0.1.1'
3
+ STRING = '0.1.2'
4
4
  end
5
5
  end
@@ -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 key
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, in case the record(s) are destroyed
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: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.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-07 00:00:00 Z
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.1 transparantly stores Records in a Cache Store and retrieve those Records from the store when queried (by ID) using Active Model.
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