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 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.join(File.dirname(__FILE__), "test", "test_helper"))
27
+ require File.expand_path("./test/helper", File.dirname(__FILE__))
29
28
 
30
- Dir["test/**/*_test.rb"].each_with_index do |file, index|
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.new(options)
43
- def connection(*options)
44
- Redis.new(*options)
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
- module_function :connect, :connection, :flush, :redis, :redis=, :options, :threaded
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 first(options = {})
110
- if options[:by]
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
- def [](index)
118
- model[key[index]]
119
- end
111
+ options = _options.dup
112
+ options[:start] ||= 0
113
+ options[:limit] = [options[:start], options[:limit]] if options[:limit]
120
114
 
121
- def sort(*args)
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, options = {})
136
- options.merge!(:by => model.key("*->#{att}"))
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("*->#{options[:get]}")))
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
- clear
156
- concat(models)
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 sort(options = {})
208
- return [] unless key.exists
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
- id = key.rpop
318
- model[id] if id
280
+ model[key.rpop]
319
281
  end
320
282
 
321
283
  def shift
322
- id = key.lpop
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, db.hincrby(key, att, count))
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) if not value.empty?
771
+ ret.push(att, value) if not value.empty?
811
772
  ret
812
773
  }
813
774
 
814
775
  db.multi do
815
- db.del(key)
816
- db.hmset(key, *atts.flatten) if atts.any?
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
- db.hdel(key, att)
786
+ key.hdel(att)
826
787
  else
827
- db.hset(key, att, value)
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
- db.sismember(key[:all], id)
820
+ key[:all].sismember(id)
860
821
  end
861
822
 
862
823
  def initialize_id
863
- self.id = db.incr(self.class.key[:id]).to_s
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
- db.del(key)
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
- db.sadd(index, id)
907
- db.sadd(key[:_indices], index)
867
+ index.sadd(id)
868
+ key[:_indices].sadd(index)
908
869
  end
909
870
 
910
871
  def delete_from_indices
911
- db.smembers(key[:_indices]).each do |index|
872
+ key[:_indices].smembers.each do |index|
912
873
  db.srem(index, id)
913
874
  end
914
875
 
915
- db.del(key[:_indices])
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 = db.hget(key, att)
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 db.setnx(key[:_lock], lock_timeout)
963
- next unless lock = db.get(key[:_lock])
964
- sleep(0.5) and next unless lock_expired?(lock)
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 lock = db.getset(key[:_lock], lock_timeout)
967
- break if lock_expired?(lock)
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
- db.del(key[:_lock])
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? lock
982
- lock.to_f < Time.now.to_f
938
+ def lock_expired? timestamp
939
+ timestamp.to_f < Time.now.to_f
983
940
  end
984
941
  end
985
942
  end
@@ -1,44 +1,17 @@
1
1
  module Ohm
2
2
 
3
3
  # Represents a key in Redis.
4
- class Key < String
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("~", @redis)[self]
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}", @redis)
10
+ self.class.new("#{self}+#{other}", redis)
22
11
  end
23
12
 
24
13
  def -(other)
25
- self.class.new("#{self}-#{other}", @redis)
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
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  module Ohm
4
- VERSION = "0.1.0.rc5"
4
+ VERSION = "0.1.0.rc6"
5
5
  end
@@ -1,25 +1,25 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
1
+ # encoding: UTF-8
2
2
 
3
- class TestString < Test::Unit::TestCase
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
- should "return the parts when separated with \\r\\n" do
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
- should "accept a record separator" do
14
- assert_equal ["ax", "bx", "cx"], "axbxcx".lines("x").to_a
15
- end
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
- should "execute the passed block" do
18
- lines = ["a\r\n", "b\r\n", "c\r\n"]
19
+ test "String#lines execute the passed block" do
20
+ lines = ["a\r\n", "b\r\n", "c\r\n"]
19
21
 
20
- "a\r\nb\r\nc\r\n".lines do |line|
21
- assert_equal lines.shift, line
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