ohm 0.0.31 → 0.0.32

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -106,13 +106,13 @@ when you retrieve the value.
106
106
 
107
107
  A `set` in Redis is an unordered list, with an external behavior similar
108
108
  to that of Ruby arrays, but optimized for faster membership lookups.
109
- It's used internaly by Ohm to keep track of the instances of each model
109
+ It's used internally by Ohm to keep track of the instances of each model
110
110
  and for generating and maintaining indexes.
111
111
 
112
112
  ### list
113
113
 
114
- A `list` is like an array in Ruby. It's perfectly suited for queues and
115
- for keeping elements in order.
114
+ A `list` is like an array in Ruby. It's perfectly suited for queues
115
+ and for keeping elements in order.
116
116
 
117
117
  ### counter
118
118
 
@@ -122,16 +122,45 @@ the value, but you can not assign it. In the example above, we used a
122
122
  counter attribute for tracking votes. As the incr and decr operations
123
123
  are atomic, you can rest assured a vote won't be counted twice.
124
124
 
125
+ Persistence strategy
126
+ --------------------
127
+
128
+ The attributes declared with `attribute` are only persisted after
129
+ calling `save`. If the object is in an invalid state, no value is sent
130
+ to Redis (see the section on **Validations** below).
131
+
132
+ Operations on attributes of type `list`, `set` and `counter` are
133
+ possible only after the object is created (when it has an assigned
134
+ `id`). Any operation on these kinds of attributes is performed
135
+ immediately, without running the object validations. This design yields
136
+ better performance than running the validations on each operation or
137
+ buffering the operations and waiting for a call to `save`.
138
+
139
+ For most use cases, this pattern doesn't represent a problem.
140
+ If you need to check for validity before operating on lists, sets or
141
+ counters, you can use this pattern:
142
+
143
+ if event.valid?
144
+ event.comments << "Great event!"
145
+ end
146
+
147
+ If you are saving the object, this will suffice:
148
+
149
+ if event.save
150
+ event.comments << "Wonderful event!"
151
+ end
152
+
153
+
125
154
  Associations
126
155
  ------------
127
156
 
128
157
  Ohm lets you use collections (lists and sets) to represent associations.
129
- For this, you only need to provide a second paramenter when declaring a
158
+ For this, you only need to provide a second parameter when declaring a
130
159
  list or a set:
131
160
 
132
161
  set :attendees, Person
133
162
 
134
- After this, everytime you refer to `event.attendees` you will be talking
163
+ After this, every time you refer to `event.attendees` you will be talking
135
164
  about instances of the model `Person`. If you want to get the raw values
136
165
  of the set, you can use `event.attendees.raw`.
137
166
 
data/lib/ohm.rb CHANGED
@@ -183,6 +183,8 @@ module Ohm
183
183
  db.rpush(key, value)
184
184
  end
185
185
 
186
+ alias push <<
187
+
186
188
  # @return [String] Return and remove the last element of the list.
187
189
  def pop
188
190
  db.rpop(key)
@@ -353,16 +355,6 @@ module Ohm
353
355
  end
354
356
  end
355
357
 
356
- class RedefinitionError < Error
357
- def initialize(att)
358
- @att = att
359
- end
360
-
361
- def message
362
- "Cannot redefine #{@att.inspect}"
363
- end
364
- end
365
-
366
358
  @@attributes = Hash.new { |hash, key| hash[key] = [] }
367
359
  @@collections = Hash.new { |hash, key| hash[key] = [] }
368
360
  @@counters = Hash.new { |hash, key| hash[key] = [] }
@@ -379,8 +371,6 @@ module Ohm
379
371
  #
380
372
  # @param name [Symbol] Name of the attribute.
381
373
  def self.attribute(name)
382
- raise RedefinitionError, name if attributes.include?(name)
383
-
384
374
  define_method(name) do
385
375
  read_local(name)
386
376
  end
@@ -389,7 +379,7 @@ module Ohm
389
379
  write_local(name, value)
390
380
  end
391
381
 
392
- attributes << name
382
+ attributes << name unless attributes.include?(name)
393
383
  end
394
384
 
395
385
  # Defines a counter attribute for the model. This attribute can't be assigned, only incremented
@@ -397,13 +387,11 @@ module Ohm
397
387
  #
398
388
  # @param name [Symbol] Name of the counter.
399
389
  def self.counter(name)
400
- raise RedefinitionError, name if counters.include?(name)
401
-
402
390
  define_method(name) do
403
391
  read_local(name).to_i
404
392
  end
405
393
 
406
- counters << name
394
+ counters << name unless counters.include?(name)
407
395
  end
408
396
 
409
397
  # Defines a list attribute for the model. It can be accessed only after the model instance
@@ -411,10 +399,8 @@ module Ohm
411
399
  #
412
400
  # @param name [Symbol] Name of the list.
413
401
  def self.list(name, model = nil)
414
- raise RedefinitionError, name if collections.include?(name)
415
-
416
402
  attr_list_reader(name, model)
417
- collections << name
403
+ collections << name unless collections.include?(name)
418
404
  end
419
405
 
420
406
  # Defines a set attribute for the model. It can be accessed only after the model instance
@@ -423,10 +409,8 @@ module Ohm
423
409
  #
424
410
  # @param name [Symbol] Name of the set.
425
411
  def self.set(name, model = nil)
426
- raise RedefinitionError, name if collections.include?(name)
427
-
428
412
  attr_set_reader(name, model)
429
- collections << name
413
+ collections << name unless collections.include?(name)
430
414
  end
431
415
 
432
416
  # Creates an index (a set) that will be used for finding instances.
@@ -445,9 +429,7 @@ module Ohm
445
429
  #
446
430
  # @param name [Symbol] Name of the attribute to be indexed.
447
431
  def self.index(att)
448
- raise RedefinitionError, att if indices.include?(att)
449
-
450
- indices << att
432
+ indices << att unless indices.include?(att)
451
433
  end
452
434
 
453
435
  def self.attr_list_reader(name, model = nil)
@@ -602,7 +584,7 @@ module Ohm
602
584
  false
603
585
  end
604
586
 
605
- # Lock the object before ejecuting the block, and release it once the block is done.
587
+ # Lock the object before executing the block, and release it once the block is done.
606
588
  def mutex
607
589
  lock!
608
590
  yield
@@ -761,9 +743,18 @@ module Ohm
761
743
  end
762
744
 
763
745
  # Lock the object so no other instances can modify it.
746
+ # This method implements the design pattern for locks
747
+ # described at: http://code.google.com/p/redis/wiki/SetnxCommand
748
+ #
764
749
  # @see Model#mutex
765
750
  def lock!
766
- lock = db.setnx(key(:_lock), 1) until lock == 1
751
+ until db.setnx(key(:_lock), lock_timeout)
752
+ next unless lock = db.get(key(:_lock))
753
+ sleep(0.5) and next unless lock_expired?(lock)
754
+
755
+ break unless lock = db.getset(key(:_lock), lock_timeout)
756
+ break if lock_expired?(lock)
757
+ end
767
758
  end
768
759
 
769
760
  # Release the lock.
@@ -771,5 +762,13 @@ module Ohm
771
762
  def unlock!
772
763
  db.del(key(:_lock))
773
764
  end
765
+
766
+ def lock_timeout
767
+ Time.now.to_f + 1
768
+ end
769
+
770
+ def lock_expired? lock
771
+ lock.to_f < Time.now.to_f
772
+ end
774
773
  end
775
774
  end
data/lib/ohm/redis.rb CHANGED
@@ -7,19 +7,19 @@
7
7
  # http://github.com/ezmobius/redis-rb/
8
8
  require 'socket'
9
9
 
10
- begin
11
- if (RUBY_VERSION >= '1.9')
12
- require 'timeout'
13
- RedisTimer = Timeout
14
- else
15
- require 'system_timer'
16
- RedisTimer = SystemTimer
10
+ module Ohm
11
+ begin
12
+ if (RUBY_VERSION >= '1.9')
13
+ require 'timeout'
14
+ RedisTimer = Timeout
15
+ else
16
+ require 'system_timer'
17
+ RedisTimer = SystemTimer
18
+ end
19
+ rescue LoadError
20
+ RedisTimer = nil
17
21
  end
18
- rescue LoadError
19
- RedisTimer = nil
20
- end
21
22
 
22
- module Ohm
23
23
  class Redis
24
24
  class ProtocolError < RuntimeError
25
25
  def initialize(reply_type)
@@ -42,6 +42,7 @@ module Ohm
42
42
  :smove => true,
43
43
  :srem => true,
44
44
  :zadd => true,
45
+ :zincrby => true,
45
46
  :zrem => true,
46
47
  :zscore => true
47
48
  }
@@ -136,13 +137,13 @@ module Ohm
136
137
  # socket instead will be supported anyway.
137
138
  if @timeout != 0 and RedisTimer
138
139
  begin
139
- @sock = TCPSocket.new(host, port, 0)
140
+ @sock = TCPSocket.new(host, port)
140
141
  rescue Timeout::Error
141
142
  @sock = nil
142
143
  raise Timeout::Error, "Timeout connecting to the server"
143
144
  end
144
145
  else
145
- @sock = TCPSocket.new(host, port, 0)
146
+ @sock = TCPSocket.new(host, port)
146
147
  end
147
148
 
148
149
  @sock.setsockopt Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1
@@ -184,7 +185,7 @@ module Ohm
184
185
  def call_command(argv)
185
186
  connect unless connected?
186
187
  raw_call_command(argv.dup)
187
- rescue Errno::ECONNRESET, Errno::EPIPE
188
+ rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED
188
189
  if reconnect
189
190
  raw_call_command(argv.dup)
190
191
  else
data/test/indices_test.rb CHANGED
@@ -165,4 +165,37 @@ class IndicesTest < Test::Unit::TestCase
165
165
  assert_equal [user], User.find(:update => "CORRECTED - UPDATE 2-Suspected US missile strike kills 5 in Pakistan")
166
166
  end
167
167
  end
168
+
169
+ context "New indices" do
170
+ should "populate a new index when the model is saved" do
171
+ class Event < Ohm::Model
172
+ attribute :name
173
+ end
174
+
175
+ foo = Event.create(:name => "Foo")
176
+
177
+ assert_raise(Ohm::Model::IndexNotFound) { Event.find(:name => "Foo") }
178
+
179
+ class Event < Ohm::Model
180
+ index :name
181
+ end
182
+
183
+ # Find works correctly once the index is added.
184
+ assert_nothing_raised { Event.find(:name => "Foo") }
185
+
186
+ # The index was added after foo was created.
187
+ assert Event.find(:name => "Foo").empty?
188
+
189
+ bar = Event.create(:name => "Bar")
190
+
191
+ # Bar was indexed properly.
192
+ assert_equal bar, Event.find(:name => "Bar").first
193
+
194
+ # Saving all the objects populates the indices.
195
+ Event.all.each { |e| e.save }
196
+
197
+ # Now foo is indexed.
198
+ assert_equal foo, Event.find(:name => "Foo").first
199
+ end
200
+ end
168
201
  end
data/test/model_test.rb CHANGED
@@ -99,8 +99,8 @@ class TestRedis < Test::Unit::TestCase
99
99
  end
100
100
 
101
101
  context "Model definition" do
102
- should "raise if an attribute is redefined" do
103
- assert_raise Ohm::Model::RedefinitionError do
102
+ should "not raise if an attribute is redefined" do
103
+ assert_nothing_raised do
104
104
  class RedefinedModel < Ohm::Model
105
105
  attribute :name
106
106
  attribute :name
@@ -108,8 +108,8 @@ class TestRedis < Test::Unit::TestCase
108
108
  end
109
109
  end
110
110
 
111
- should "raise if a counter is redefined" do
112
- assert_raise Ohm::Model::RedefinitionError do
111
+ should "not raise if a counter is redefined" do
112
+ assert_nothing_raised do
113
113
  class RedefinedModel < Ohm::Model
114
114
  counter :age
115
115
  counter :age
@@ -117,8 +117,8 @@ class TestRedis < Test::Unit::TestCase
117
117
  end
118
118
  end
119
119
 
120
- should "raise if a list is redefined" do
121
- assert_raise Ohm::Model::RedefinitionError do
120
+ should "not raise if a list is redefined" do
121
+ assert_nothing_raised do
122
122
  class RedefinedModel < Ohm::Model
123
123
  list :todo
124
124
  list :todo
@@ -126,8 +126,8 @@ class TestRedis < Test::Unit::TestCase
126
126
  end
127
127
  end
128
128
 
129
- should "raise if a set is redefined" do
130
- assert_raise Ohm::Model::RedefinitionError do
129
+ should "not raise if a set is redefined" do
130
+ assert_nothing_raised do
131
131
  class RedefinedModel < Ohm::Model
132
132
  set :friends
133
133
  set :friends
@@ -135,8 +135,8 @@ class TestRedis < Test::Unit::TestCase
135
135
  end
136
136
  end
137
137
 
138
- should "raise if a collection is redefined" do
139
- assert_raise Ohm::Model::RedefinitionError do
138
+ should "not raise if a collection is redefined" do
139
+ assert_nothing_raised do
140
140
  class RedefinedModel < Ohm::Model
141
141
  list :toys
142
142
  set :toys
@@ -144,8 +144,8 @@ class TestRedis < Test::Unit::TestCase
144
144
  end
145
145
  end
146
146
 
147
- should "raise if a index is redefined" do
148
- assert_raise Ohm::Model::RedefinitionError do
147
+ should "not raise if a index is redefined" do
148
+ assert_nothing_raised do
149
149
  class RedefinedModel < Ohm::Model
150
150
  attribute :color
151
151
  index :color
@@ -453,6 +453,13 @@ class TestRedis < Test::Unit::TestCase
453
453
  assert @post.comments.all.kind_of?(Array)
454
454
  end
455
455
 
456
+ should "append elements with push" do
457
+ @post.comments.push "1"
458
+ @post.comments << "2"
459
+
460
+ assert_equal ["1", "2"], @post.comments.all
461
+ end
462
+
456
463
  should "keep the inserting order" do
457
464
  @post.comments << "1"
458
465
  @post.comments << "2"
data/test/mutex_test.rb CHANGED
@@ -32,5 +32,58 @@ class TestMutex < Test::Unit::TestCase
32
32
  p2.join
33
33
  assert t2 > t1
34
34
  end
35
+
36
+ should "allow an instance to lock a record if the previous lock is expired" do
37
+ @p1.send(:lock!)
38
+ @p2.mutex do
39
+ assert true
40
+ end
41
+ end
42
+
43
+ should "work if two clients are fighting for the lock" do
44
+ @p1.send(:lock!)
45
+ @p3 = Person[1]
46
+ @p4 = Person[1]
47
+
48
+ assert_nothing_raised do
49
+ p1 = Thread.new { @p1.mutex {} }
50
+ p2 = Thread.new { @p2.mutex {} }
51
+ p3 = Thread.new { @p3.mutex {} }
52
+ p4 = Thread.new { @p4.mutex {} }
53
+ p1.join
54
+ p2.join
55
+ p3.join
56
+ p4.join
57
+ end
58
+ end
59
+
60
+ should "yield the right result after a lock fight" do
61
+ class Candidate < Ohm::Model
62
+ attribute :name
63
+ counter :votes
64
+ end
65
+
66
+ @candidate = Candidate.create :name => "Foo"
67
+ @candidate.send(:lock!)
68
+
69
+ threads = []
70
+
71
+ n = 10
72
+ m = 9
73
+
74
+ n.times do |i|
75
+ threads << Thread.new do
76
+ m.times do |i|
77
+ @candidate.mutex do
78
+ sleep 0.1
79
+ @candidate.incr(:votes)
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ threads.each { |t| t.join }
86
+ assert_equal n * m, @candidate.votes
87
+ end
35
88
  end
36
89
  end
data/test/redis_test.rb CHANGED
@@ -386,6 +386,23 @@ class RedisTest < Test::Unit::TestCase
386
386
  end
387
387
  end
388
388
 
389
+ should "increment by a certain amount the score of a zset ZINCRBY" do
390
+ assert_equal 0, @r.zcard("league")
391
+
392
+ @r.zincrby "league", 1, "foo"
393
+ assert_equal "1", @r.zscore("league", "foo")
394
+
395
+ assert_equal 1, @r.zcard("league")
396
+
397
+ @r.zincrby "league", 10, "foo"
398
+ assert_equal "11", @r.zscore("league", "foo")
399
+
400
+ @r.set "bar", "2"
401
+ assert_raises do
402
+ @r.zincrby "bar", 2, "baz"
403
+ end
404
+ end
405
+
389
406
  should "provide info" do
390
407
  %w(last_save_time redis_version total_connections_received connected_clients total_commands_processed connected_slaves uptime_in_seconds used_memory uptime_in_days changes_since_last_save).each do |x|
391
408
  assert @r.info.keys.include?(x)
data/test/test_helper.rb CHANGED
@@ -8,5 +8,5 @@ end
8
8
  require "contest"
9
9
  require File.dirname(__FILE__) + "/../lib/ohm"
10
10
 
11
- Ohm.connect(:port => 6381)
11
+ Ohm.connect(:port => 6381, :db => 1, :timeout => 3)
12
12
  Ohm.flush
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ohm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.31
4
+ version: 0.0.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michel Martens
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-11-26 00:00:00 -03:00
13
+ date: 2010-01-13 00:00:00 -03:00
14
14
  default_executable:
15
15
  dependencies: []
16
16