ohm 0.1.0.rc5 → 0.1.0.rc6
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/Rakefile +2 -13
- data/lib/ohm.rb +54 -97
- data/lib/ohm/key.rb +4 -31
- data/lib/ohm/version.rb +1 -1
- data/test/1.8.6_test.rb +18 -18
- data/test/associations_test.rb +100 -0
- data/test/connection_test.rb +33 -21
- data/test/errors_test.rb +88 -88
- data/test/hash_key_test.rb +16 -23
- data/test/helper.rb +25 -0
- data/test/indices_test.rb +177 -183
- data/test/json_test.rb +67 -0
- data/test/model_test.rb +675 -868
- data/test/mutex_test.rb +65 -70
- data/test/pattern_test.rb +8 -6
- data/test/upgrade_script_test.rb +41 -47
- data/test/validations_test.rb +180 -164
- data/test/wrapper_test.rb +7 -5
- metadata +26 -10
- data/test/all_tests.rb +0 -2
- data/test/test_helper.rb +0 -53
data/Rakefile
CHANGED
@@ -20,22 +20,11 @@ desc "Stop the Redis server"
|
|
20
20
|
task :stop do
|
21
21
|
if File.exists?(REDIS_PID)
|
22
22
|
system "kill #{File.read(REDIS_PID)}"
|
23
|
-
system "rm #{REDIS_PID}"
|
24
23
|
end
|
25
24
|
end
|
26
25
|
|
27
26
|
task :test do
|
28
|
-
require File.expand_path(File.
|
27
|
+
require File.expand_path("./test/helper", File.dirname(__FILE__))
|
29
28
|
|
30
|
-
Dir["test
|
31
|
-
ENV["REDIS_URL"] = "redis://127.0.0.1:6379/#{index}"
|
32
|
-
|
33
|
-
fork do
|
34
|
-
load file
|
35
|
-
end
|
36
|
-
|
37
|
-
exit $?.exitstatus unless $?.success?
|
38
|
-
end
|
39
|
-
|
40
|
-
Process.waitall
|
29
|
+
Cutest.run(Dir["test/*_test.rb"])
|
41
30
|
end
|
data/lib/ohm.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "base64"
|
4
4
|
require "redis"
|
5
|
+
require "nest"
|
5
6
|
|
6
7
|
require File.join(File.dirname(__FILE__), "ohm", "pattern")
|
7
8
|
require File.join(File.dirname(__FILE__), "ohm", "validations")
|
@@ -11,15 +12,15 @@ require File.join(File.dirname(__FILE__), "ohm", "key")
|
|
11
12
|
module Ohm
|
12
13
|
|
13
14
|
# Provides access to the Redis database. This is shared accross all models and instances.
|
14
|
-
def redis
|
15
|
+
def self.redis
|
15
16
|
threaded[:redis] ||= connection(*options)
|
16
17
|
end
|
17
18
|
|
18
|
-
def redis=(connection)
|
19
|
+
def self.redis=(connection)
|
19
20
|
threaded[:redis] = connection
|
20
21
|
end
|
21
22
|
|
22
|
-
def threaded
|
23
|
+
def self.threaded
|
23
24
|
Thread.current[:ohm] ||= {}
|
24
25
|
end
|
25
26
|
|
@@ -32,31 +33,29 @@ module Ohm
|
|
32
33
|
# @option options [#to_s] :timeout (0) Database timeout in seconds.
|
33
34
|
# @example Connect to a database in port 6380.
|
34
35
|
# Ohm.connect(:port => 6380)
|
35
|
-
def connect(*options)
|
36
|
+
def self.connect(*options)
|
36
37
|
self.redis = nil
|
37
38
|
@options = options
|
38
39
|
end
|
39
40
|
|
40
41
|
# Return a connection to Redis.
|
41
42
|
#
|
42
|
-
# This is a wapper around Redis.
|
43
|
-
def connection(*options)
|
44
|
-
Redis.
|
43
|
+
# This is a wapper around Redis.connect(options)
|
44
|
+
def self.connection(*options)
|
45
|
+
Redis.connect(*options)
|
45
46
|
end
|
46
47
|
|
47
|
-
def options
|
48
|
+
def self.options
|
48
49
|
@options = [] unless defined? @options
|
49
50
|
@options
|
50
51
|
end
|
51
52
|
|
52
53
|
# Clear the database.
|
53
|
-
def flush
|
54
|
+
def self.flush
|
54
55
|
redis.flushdb
|
55
56
|
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
Error = Class.new(StandardError)
|
58
|
+
class Error < StandardError; end
|
60
59
|
|
61
60
|
class Model
|
62
61
|
|
@@ -106,20 +105,14 @@ module Ohm
|
|
106
105
|
self << model
|
107
106
|
end
|
108
107
|
|
109
|
-
def
|
110
|
-
|
111
|
-
sort_by(options.delete(:by), options.merge(:limit => 1)).first
|
112
|
-
else
|
113
|
-
model[key.first(options)]
|
114
|
-
end
|
115
|
-
end
|
108
|
+
def sort(_options = {})
|
109
|
+
return [] unless key.exists
|
116
110
|
|
117
|
-
|
118
|
-
|
119
|
-
|
111
|
+
options = _options.dup
|
112
|
+
options[:start] ||= 0
|
113
|
+
options[:limit] = [options[:start], options[:limit]] if options[:limit]
|
120
114
|
|
121
|
-
|
122
|
-
key.sort(*args).map(&model)
|
115
|
+
key.sort(options).map(&model)
|
123
116
|
end
|
124
117
|
|
125
118
|
# Sort the model instances by the given attribute.
|
@@ -132,11 +125,14 @@ module Ohm
|
|
132
125
|
# user = User.all.sort_by(:name, :order => "ALPHA").first
|
133
126
|
# user.name == "A"
|
134
127
|
# # => true
|
135
|
-
def sort_by(att,
|
136
|
-
|
128
|
+
def sort_by(att, _options = {})
|
129
|
+
return [] unless key.exists
|
130
|
+
|
131
|
+
options = _options.dup
|
132
|
+
options.merge!(:by => model.key["*->#{att}"])
|
137
133
|
|
138
134
|
if options[:get]
|
139
|
-
key.sort(options.merge(:get => model.key
|
135
|
+
key.sort(options.merge(:get => model.key["*->#{options[:get]}"]))
|
140
136
|
else
|
141
137
|
sort(options)
|
142
138
|
end
|
@@ -146,14 +142,11 @@ module Ohm
|
|
146
142
|
key.del
|
147
143
|
end
|
148
144
|
|
149
|
-
def concat(models)
|
150
|
-
models.each { |model| add(model) }
|
151
|
-
self
|
152
|
-
end
|
153
|
-
|
154
145
|
def replace(models)
|
155
|
-
|
156
|
-
|
146
|
+
model.db.multi do
|
147
|
+
clear
|
148
|
+
models.each { |model| add(model) }
|
149
|
+
end
|
157
150
|
end
|
158
151
|
|
159
152
|
def empty?
|
@@ -204,38 +197,8 @@ module Ohm
|
|
204
197
|
apply(:sdiffstore, key, source, target)
|
205
198
|
end
|
206
199
|
|
207
|
-
def
|
208
|
-
|
209
|
-
|
210
|
-
options[:start] ||= 0
|
211
|
-
options[:limit] = [options[:start], options[:limit]] if options[:limit]
|
212
|
-
|
213
|
-
key.sort(options).map(&model)
|
214
|
-
end
|
215
|
-
|
216
|
-
# Sort the model instances by the given attribute.
|
217
|
-
#
|
218
|
-
# @example Sorting elements by name:
|
219
|
-
#
|
220
|
-
# User.create :name => "B"
|
221
|
-
# User.create :name => "A"
|
222
|
-
#
|
223
|
-
# user = User.all.sort_by(:name, :order => "ALPHA").first
|
224
|
-
# user.name == "A"
|
225
|
-
# # => true
|
226
|
-
def sort_by(att, options = {})
|
227
|
-
return [] unless key.exists
|
228
|
-
|
229
|
-
options.merge!(:by => model.key["*->#{att}"])
|
230
|
-
|
231
|
-
if options[:get]
|
232
|
-
key.sort(options.merge(:get => model.key["*->#{options[:get]}"]))
|
233
|
-
else
|
234
|
-
sort(options)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
def first(options = {})
|
200
|
+
def first(_options = {})
|
201
|
+
options = _options.dup
|
239
202
|
options.merge!(:limit => 1)
|
240
203
|
|
241
204
|
if options[:by]
|
@@ -314,13 +277,11 @@ module Ohm
|
|
314
277
|
end
|
315
278
|
|
316
279
|
def pop
|
317
|
-
|
318
|
-
model[id] if id
|
280
|
+
model[key.rpop]
|
319
281
|
end
|
320
282
|
|
321
283
|
def shift
|
322
|
-
|
323
|
-
model[id] if id
|
284
|
+
model[key.lpop]
|
324
285
|
end
|
325
286
|
|
326
287
|
def unshift(model)
|
@@ -570,7 +531,7 @@ module Ohm
|
|
570
531
|
end
|
571
532
|
|
572
533
|
def self.[](id)
|
573
|
-
new(:id => id) if exists?(id)
|
534
|
+
new(:id => id) if id && exists?(id)
|
574
535
|
end
|
575
536
|
|
576
537
|
def self.to_proc
|
@@ -675,7 +636,7 @@ module Ohm
|
|
675
636
|
# @param att [Symbol] Attribute to increment.
|
676
637
|
def incr(att, count = 1)
|
677
638
|
raise ArgumentError, "#{att.inspect} is not a counter." unless counters.include?(att)
|
678
|
-
write_local(att,
|
639
|
+
write_local(att, key.hincrby(att, count))
|
679
640
|
end
|
680
641
|
|
681
642
|
# Decrement the counter denoted by :att.
|
@@ -807,13 +768,13 @@ module Ohm
|
|
807
768
|
atts = (attributes + counters).inject([]) { |ret, att|
|
808
769
|
value = send(att).to_s
|
809
770
|
|
810
|
-
ret.push(att, value)
|
771
|
+
ret.push(att, value) if not value.empty?
|
811
772
|
ret
|
812
773
|
}
|
813
774
|
|
814
775
|
db.multi do
|
815
|
-
|
816
|
-
|
776
|
+
key.del
|
777
|
+
key.hmset(*atts.flatten) if atts.any?
|
817
778
|
end
|
818
779
|
end
|
819
780
|
end
|
@@ -822,9 +783,9 @@ module Ohm
|
|
822
783
|
write_local(att, value)
|
823
784
|
|
824
785
|
if value.to_s.empty?
|
825
|
-
|
786
|
+
key.hdel(att)
|
826
787
|
else
|
827
|
-
|
788
|
+
key.hset(att, value)
|
828
789
|
end
|
829
790
|
end
|
830
791
|
|
@@ -856,11 +817,11 @@ module Ohm
|
|
856
817
|
end
|
857
818
|
|
858
819
|
def self.exists?(id)
|
859
|
-
|
820
|
+
key[:all].sismember(id)
|
860
821
|
end
|
861
822
|
|
862
823
|
def initialize_id
|
863
|
-
|
824
|
+
@id ||= self.class.key[:id].incr.to_s
|
864
825
|
end
|
865
826
|
|
866
827
|
def db
|
@@ -876,7 +837,7 @@ module Ohm
|
|
876
837
|
end
|
877
838
|
|
878
839
|
def delete_model_membership
|
879
|
-
|
840
|
+
key.del
|
880
841
|
self.class.all.delete(self)
|
881
842
|
end
|
882
843
|
|
@@ -903,16 +864,16 @@ module Ohm
|
|
903
864
|
|
904
865
|
def add_to_index(att, value = send(att))
|
905
866
|
index = index_key_for(att, value)
|
906
|
-
|
907
|
-
|
867
|
+
index.sadd(id)
|
868
|
+
key[:_indices].sadd(index)
|
908
869
|
end
|
909
870
|
|
910
871
|
def delete_from_indices
|
911
|
-
|
872
|
+
key[:_indices].smembers.each do |index|
|
912
873
|
db.srem(index, id)
|
913
874
|
end
|
914
875
|
|
915
|
-
|
876
|
+
key[:_indices].del
|
916
877
|
end
|
917
878
|
|
918
879
|
def read_local(att)
|
@@ -925,7 +886,7 @@ module Ohm
|
|
925
886
|
|
926
887
|
def read_remote(att)
|
927
888
|
unless new?
|
928
|
-
value =
|
889
|
+
value = key.hget(att)
|
929
890
|
value.respond_to?(:force_encoding) ?
|
930
891
|
value.force_encoding("UTF-8") :
|
931
892
|
value
|
@@ -959,27 +920,23 @@ module Ohm
|
|
959
920
|
#
|
960
921
|
# @see Model#mutex
|
961
922
|
def lock!
|
962
|
-
until
|
963
|
-
next unless
|
964
|
-
sleep(0.
|
923
|
+
until key[:_lock].setnx(Time.now.to_f + 0.5)
|
924
|
+
next unless timestamp = key[:_lock].get
|
925
|
+
sleep(0.1) and next unless lock_expired?(timestamp)
|
965
926
|
|
966
|
-
break unless
|
967
|
-
break if lock_expired?(
|
927
|
+
break unless timestamp = key[:_lock].getset(Time.now.to_f + 0.5)
|
928
|
+
break if lock_expired?(timestamp)
|
968
929
|
end
|
969
930
|
end
|
970
931
|
|
971
932
|
# Release the lock.
|
972
933
|
# @see Model#mutex
|
973
934
|
def unlock!
|
974
|
-
|
975
|
-
end
|
976
|
-
|
977
|
-
def lock_timeout
|
978
|
-
Time.now.to_f + 1
|
935
|
+
key[:_lock].del
|
979
936
|
end
|
980
937
|
|
981
|
-
def lock_expired?
|
982
|
-
|
938
|
+
def lock_expired? timestamp
|
939
|
+
timestamp.to_f < Time.now.to_f
|
983
940
|
end
|
984
941
|
end
|
985
942
|
end
|
data/lib/ohm/key.rb
CHANGED
@@ -1,44 +1,17 @@
|
|
1
1
|
module Ohm
|
2
2
|
|
3
3
|
# Represents a key in Redis.
|
4
|
-
class Key <
|
5
|
-
attr :redis
|
6
|
-
|
7
|
-
def initialize(name, redis = nil)
|
8
|
-
@redis = redis
|
9
|
-
super(name.to_s)
|
10
|
-
end
|
11
|
-
|
12
|
-
def [](key)
|
13
|
-
self.class.new("#{self}:#{key}", @redis)
|
14
|
-
end
|
15
|
-
|
4
|
+
class Key < Nest
|
16
5
|
def volatile
|
17
|
-
self.index("~") == 0 ? self : self.class.new("~",
|
6
|
+
self.index("~") == 0 ? self : self.class.new("~", redis)[self]
|
18
7
|
end
|
19
8
|
|
20
9
|
def +(other)
|
21
|
-
self.class.new("#{self}+#{other}",
|
10
|
+
self.class.new("#{self}+#{other}", redis)
|
22
11
|
end
|
23
12
|
|
24
13
|
def -(other)
|
25
|
-
self.class.new("#{self}-#{other}",
|
26
|
-
end
|
27
|
-
|
28
|
-
[:append, :blpop, :brpop, :decr, :decrby, :del, :exists, :expire,
|
29
|
-
:expireat, :get, :getset, :hdel, :hexists, :hget, :hgetall,
|
30
|
-
:hincrby, :hkeys, :hlen, :hmget, :hmset, :hset, :hvals, :incr,
|
31
|
-
:incrby, :lindex, :llen, :lpop, :lpush, :lrange, :lrem, :lset,
|
32
|
-
:ltrim, :move, :rename, :renamenx, :rpop, :rpoplpush, :rpush,
|
33
|
-
:sadd, :scard, :sdiff, :sdiffstore, :set, :setex, :setnx, :sinter,
|
34
|
-
:sinterstore, :sismember, :smembers, :smove, :sort, :spop,
|
35
|
-
:srandmember, :srem, :substr, :sunion, :sunionstore, :ttl, :type,
|
36
|
-
:zadd, :zcard, :zincrby, :zinterstore, :zrange, :zrangebyscore,
|
37
|
-
:zrank, :zrem, :zremrangebyrank, :zremrangebyscore, :zrevrange,
|
38
|
-
:zrevrank, :zscore, :zunionstore].each do |meth|
|
39
|
-
define_method(meth) do |*args|
|
40
|
-
redis.send(meth, self, *args)
|
41
|
-
end
|
14
|
+
self.class.new("#{self}-#{other}", redis)
|
42
15
|
end
|
43
16
|
end
|
44
17
|
end
|
data/lib/ohm/version.rb
CHANGED
data/test/1.8.6_test.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
|
-
|
1
|
+
# encoding: UTF-8
|
2
2
|
|
3
|
-
|
4
|
-
context "String#lines" do
|
5
|
-
should "return the parts when separated with \\n" do
|
6
|
-
assert_equal ["a\n", "b\n", "c\n"], "a\nb\nc\n".lines.to_a
|
7
|
-
end
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
8
4
|
|
9
|
-
|
10
|
-
assert_equal ["a\r\n", "b\r\n", "c\r\n"], "a\r\nb\r\nc\r\n".lines.to_a
|
11
|
-
end
|
5
|
+
prepare.clear
|
12
6
|
|
13
|
-
|
14
|
-
|
15
|
-
|
7
|
+
test "String#lines should return the parts when separated with \\n" do
|
8
|
+
assert ["a\n", "b\n", "c\n"] == "a\nb\nc\n".lines.to_a
|
9
|
+
end
|
10
|
+
|
11
|
+
test "String#lines return the parts when separated with \\r\\n" do
|
12
|
+
assert ["a\r\n", "b\r\n", "c\r\n"] == "a\r\nb\r\nc\r\n".lines.to_a
|
13
|
+
end
|
14
|
+
|
15
|
+
test "String#lines accept a record separator" do
|
16
|
+
assert ["ax", "bx", "cx"] == "axbxcx".lines("x").to_a
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
test "String#lines execute the passed block" do
|
20
|
+
lines = ["a\r\n", "b\r\n", "c\r\n"]
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
22
|
+
"a\r\nb\r\nc\r\n".lines do |line|
|
23
|
+
assert lines.shift == line
|
24
24
|
end
|
25
25
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
class Person < Ohm::Model
|
6
|
+
attribute :name
|
7
|
+
end
|
8
|
+
|
9
|
+
class ::Note < Ohm::Model
|
10
|
+
attribute :content
|
11
|
+
reference :source, Post
|
12
|
+
collection :comments, Comment
|
13
|
+
list :ratings, Rating
|
14
|
+
end
|
15
|
+
|
16
|
+
class ::Comment < Ohm::Model
|
17
|
+
reference :note, Note
|
18
|
+
end
|
19
|
+
|
20
|
+
class ::Rating < Ohm::Model
|
21
|
+
attribute :value
|
22
|
+
end
|
23
|
+
|
24
|
+
class ::Editor < Ohm::Model
|
25
|
+
attribute :name
|
26
|
+
reference :post, Post
|
27
|
+
end
|
28
|
+
|
29
|
+
class ::Post < Ohm::Model
|
30
|
+
reference :author, Person
|
31
|
+
collection :notes, Note, :source
|
32
|
+
collection :editors, Editor
|
33
|
+
end
|
34
|
+
|
35
|
+
setup do
|
36
|
+
@post = Post.create
|
37
|
+
end
|
38
|
+
|
39
|
+
test "return an instance of Person if author_id has a valid id" do
|
40
|
+
@post.author_id = Person.create(:name => "Albert").id
|
41
|
+
@post.save
|
42
|
+
assert "Albert" == Post[@post.id].author.name
|
43
|
+
end
|
44
|
+
|
45
|
+
test "assign author_id if author is sent a valid instance" do
|
46
|
+
@post.author = Person.create(:name => "Albert")
|
47
|
+
@post.save
|
48
|
+
assert "Albert" == Post[@post.id].author.name
|
49
|
+
end
|
50
|
+
|
51
|
+
test "assign nil if nil is passed to author" do
|
52
|
+
@post.author = nil
|
53
|
+
@post.save
|
54
|
+
assert Post[@post.id].author.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
test "be cached in an instance variable" do
|
58
|
+
@author = Person.create(:name => "Albert")
|
59
|
+
@post.update(:author => @author)
|
60
|
+
|
61
|
+
assert @author == @post.author
|
62
|
+
assert @post.author.object_id == @post.author.object_id
|
63
|
+
|
64
|
+
@post.update(:author => Person.create(:name => "Bertrand"))
|
65
|
+
|
66
|
+
assert "Bertrand" == @post.author.name
|
67
|
+
assert @post.author.object_id == @post.author.object_id
|
68
|
+
|
69
|
+
@post.update(:author_id => Person.create(:name => "Charles").id)
|
70
|
+
|
71
|
+
assert "Charles" == @post.author.name
|
72
|
+
end
|
73
|
+
|
74
|
+
setup do
|
75
|
+
@post = Post.create
|
76
|
+
@note = Note.create(:content => "Interesting stuff", :source => @post)
|
77
|
+
@comment = Comment.create(:note => @note)
|
78
|
+
end
|
79
|
+
|
80
|
+
test "return a set of notes" do
|
81
|
+
assert @note.source == @post
|
82
|
+
assert @note == @post.notes.first
|
83
|
+
end
|
84
|
+
|
85
|
+
test "return a set of comments" do
|
86
|
+
assert @comment == @note.comments.first
|
87
|
+
end
|
88
|
+
|
89
|
+
test "return a list of ratings" do
|
90
|
+
@rating = Rating.create(:value => 5)
|
91
|
+
@note.ratings << @rating
|
92
|
+
|
93
|
+
assert @rating == @note.ratings.first
|
94
|
+
end
|
95
|
+
|
96
|
+
test "default to the current class name" do
|
97
|
+
@editor = Editor.create(:name => "Albert", :post => @post)
|
98
|
+
|
99
|
+
assert @editor == @post.editors.first
|
100
|
+
end
|