ohm 0.0.31 → 0.0.32

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/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