ohm 0.0.2 → 0.0.3

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/lib/ohm.rb CHANGED
@@ -1,48 +1,14 @@
1
1
  require "rubygems"
2
2
  require "redis"
3
+ require File.join(File.dirname(__FILE__), "ohm", "validations")
3
4
 
4
5
  module Ohm
5
- module Validations
6
- def valid?
7
- errors.clear
8
- validate
9
- errors.empty?
10
- end
11
-
12
- def validate
13
- end
14
-
15
- def errors
16
- @errors ||= []
17
- end
18
-
19
- private
20
-
21
- def assert_format(att, format)
22
- if assert_present(att)
23
- assert attribute(att).match(format), [att, :format]
24
- end
25
- end
26
-
27
- def assert_present(att)
28
- if assert_not_nil(att)
29
- assert attribute(att).any?, [att, :empty]
30
- end
31
- end
32
-
33
- def assert_not_nil(att)
34
- assert attribute(att), [att, :nil]
35
- end
36
-
37
- def assert(value, error)
38
- value or errors.push(error) && false
39
- end
40
-
41
- def attribute(att)
42
- instance_variable_get("@#{att}")
43
- end
6
+ def key(*args)
7
+ args.join(":")
44
8
  end
45
9
 
10
+ module_function :key
11
+
46
12
  module Attributes
47
13
  class Collection < Array
48
14
  attr_accessor :key, :db
@@ -84,18 +50,34 @@ module Ohm
84
50
  end
85
51
 
86
52
  class Model
53
+ module Validations
54
+ include Ohm::Validations
55
+
56
+ def assert_unique(attrs)
57
+ index_key = index_key_for(attrs, read_locals(attrs))
58
+ assert(db.set_count(index_key).zero? || db.set_member?(index_key, id), [attrs, :not_unique])
59
+ end
60
+ end
61
+
87
62
  include Validations
88
63
 
89
64
  ModelIsNew = Class.new(StandardError)
90
65
 
91
66
  @@attributes = Hash.new { |hash, key| hash[key] = [] }
92
67
  @@collections = Hash.new { |hash, key| hash[key] = [] }
68
+ @@indices = Hash.new { |hash, key| hash[key] = [] }
93
69
 
94
70
  attr_accessor :id
95
71
 
96
72
  def self.attribute(name)
97
- attr_writer(name)
98
- attr_value_reader(name)
73
+ define_method(name) do
74
+ read_local(name)
75
+ end
76
+
77
+ define_method(:"#{name}=") do |value|
78
+ write_local(name, value)
79
+ end
80
+
99
81
  attributes << name
100
82
  end
101
83
 
@@ -109,13 +91,8 @@ module Ohm
109
91
  collections << name
110
92
  end
111
93
 
112
- # TODO Encapsulate access to db?
113
- def self.attr_value_reader(name)
114
- class_eval <<-EOS
115
- def #{name}
116
- @#{name} ||= db[key("#{name}")]
117
- end
118
- EOS
94
+ def self.index(attrs)
95
+ indices << attrs
119
96
  end
120
97
 
121
98
  def self.attr_list_reader(name)
@@ -139,9 +116,7 @@ module Ohm
139
116
  end
140
117
 
141
118
  def self.all
142
- filter(:all).map do |id|
143
- new(:id => id)
144
- end
119
+ filter(:all)
145
120
  end
146
121
 
147
122
  def self.attributes
@@ -152,11 +127,24 @@ module Ohm
152
127
  @@collections[self]
153
128
  end
154
129
 
130
+ def self.indices
131
+ @@indices[self]
132
+ end
133
+
155
134
  def self.create(*args)
156
135
  new(*args).create
157
136
  end
158
137
 
138
+ # TODO Add a method that receives several arguments and returns a
139
+ # string with the values separated by colons.
140
+ def self.find(attribute, value)
141
+ # filter("#{attribute}:#{value}")
142
+ filter(Ohm.key(attribute, value))
143
+ end
144
+
159
145
  def initialize(attrs = {})
146
+ @_attributes = Hash.new {|hash,key| hash[key] = read_remote(key) }
147
+
160
148
  attrs.each do |key, value|
161
149
  send(:"#{key}=", value)
162
150
  end
@@ -166,15 +154,18 @@ module Ohm
166
154
  return unless valid?
167
155
  initialize_id
168
156
  create_model_membership
157
+ add_to_indices
169
158
  save!
170
159
  end
171
160
 
172
161
  def save
173
162
  return unless valid?
163
+ update_indices
174
164
  save!
175
165
  end
176
166
 
177
167
  def delete
168
+ delete_from_indices
178
169
  delete_attributes(collections)
179
170
  delete_attributes(attributes)
180
171
  delete_model_membership
@@ -189,6 +180,23 @@ module Ohm
189
180
  self.class.collections
190
181
  end
191
182
 
183
+ def indices
184
+ self.class.indices
185
+ end
186
+
187
+ def ==(other)
188
+ other.key == key
189
+ rescue ModelIsNew
190
+ false
191
+ end
192
+
193
+ protected
194
+
195
+ def key(*args)
196
+ raise ModelIsNew unless id
197
+ self.class.key(id, *args)
198
+ end
199
+
192
200
  private
193
201
 
194
202
  def self.db
@@ -196,11 +204,13 @@ module Ohm
196
204
  end
197
205
 
198
206
  def self.key(*args)
199
- args.unshift(self).join(":")
207
+ Ohm.key(*args.unshift(self))
200
208
  end
201
209
 
202
210
  def self.filter(name)
203
- db.set_members(key(name))
211
+ db.set_members(key(name)).map do |id|
212
+ new(:id => id)
213
+ end
204
214
  end
205
215
 
206
216
  def self.exists?(id)
@@ -215,11 +225,6 @@ module Ohm
215
225
  self.class.db
216
226
  end
217
227
 
218
- def key(*args)
219
- raise ModelIsNew unless id
220
- self.class.key(id, *args)
221
- end
222
-
223
228
  def delete_attributes(atts)
224
229
  atts.each do |att|
225
230
  db.delete(key(att))
@@ -235,8 +240,57 @@ module Ohm
235
240
  end
236
241
 
237
242
  def save!
238
- attributes.each { |att| db[key(att)] = send(att) }
243
+ attributes.each { |att| write_remote(att, send(att)) }
239
244
  self
240
245
  end
246
+
247
+ def update_indices
248
+ delete_from_indices
249
+ add_to_indices
250
+ end
251
+
252
+ def add_to_indices
253
+ indices.each do |attrs|
254
+ db.set_add(index_key_for(attrs, read_locals(attrs)), id)
255
+ end
256
+ end
257
+
258
+ def delete_from_indices
259
+ indices.each do |attrs|
260
+ db.set_delete(index_key_for(attrs, read_remotes(attrs)), id)
261
+ end
262
+ end
263
+
264
+ def read_local(att)
265
+ @_attributes[att]
266
+ end
267
+
268
+ def write_local(att, value)
269
+ @_attributes[att] = value
270
+ end
271
+
272
+ def read_remote(att)
273
+ id && db[key(att)]
274
+ end
275
+
276
+ def write_remote(att, value)
277
+ db[key(att)] = value
278
+ end
279
+
280
+ def read_locals(attrs)
281
+ attrs.map do |att|
282
+ read_local(att)
283
+ end
284
+ end
285
+
286
+ def read_remotes(attrs)
287
+ attrs.map do |att|
288
+ read_remote(att)
289
+ end
290
+ end
291
+
292
+ def index_key_for(attrs, values)
293
+ self.class.key *(attrs + values)
294
+ end
241
295
  end
242
296
  end
@@ -0,0 +1,38 @@
1
+ module Ohm
2
+ module Validations
3
+ def valid?
4
+ errors.clear
5
+ validate
6
+ errors.empty?
7
+ end
8
+
9
+ def validate
10
+ end
11
+
12
+ def errors
13
+ @errors ||= []
14
+ end
15
+
16
+ protected
17
+
18
+ def assert_format(att, format)
19
+ if assert_present(att)
20
+ assert send(att).match(format), [att, :format]
21
+ end
22
+ end
23
+
24
+ def assert_present(att)
25
+ if assert_not_nil(att)
26
+ assert send(att).any?, [att, :empty]
27
+ end
28
+ end
29
+
30
+ def assert_not_nil(att)
31
+ assert send(att), [att, :nil]
32
+ end
33
+
34
+ def assert(value, error)
35
+ value or errors.push(error) && false
36
+ end
37
+ end
38
+ end
@@ -29,4 +29,24 @@ benchmark "ruby array push" do
29
29
  array.push(1)
30
30
  end
31
31
 
32
- run 10000
32
+ $redis.set_add("bar", 1)
33
+ $redis.set_add("bar", 2)
34
+
35
+ benchmark "retrieve a set of two members" do
36
+ $redis.set_members("bar")
37
+ end
38
+
39
+ benchmark "retrieve membership status and set count" do
40
+ $redis.set_count("bar")
41
+ $redis.set_member?("bar", "1")
42
+ end
43
+
44
+ benchmark "retrieve set count" do
45
+ $redis.set_count("bar").zero?
46
+ end
47
+
48
+ benchmark "retrieve membership status" do
49
+ $redis.set_member?("bar", "1")
50
+ end
51
+
52
+ run 20_000
Binary file
@@ -1 +1 @@
1
- 6416
1
+ 26947
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class IndicesTest < Test::Unit::TestCase
4
+ class User < Ohm::Model
5
+ attribute :email
6
+
7
+ index [:email]
8
+ end
9
+
10
+ context "A model with an indexed attribute" do
11
+ setup do
12
+ $redis.flush_db
13
+
14
+ @user1 = User.create(:email => "foo")
15
+ @user2 = User.create(:email => "bar")
16
+ end
17
+
18
+ should "be able to find by the given attribute" do
19
+ assert_equal [@user1], User.find(:email, "foo").to_a
20
+ end
21
+
22
+ should "update indices when changing attribute values" do
23
+ @user1.email = "baz"
24
+ @user1.save
25
+
26
+ assert_equal [], User.find(:email, "foo").to_a
27
+ assert_equal [@user1], User.find(:email, "baz").to_a
28
+ end
29
+
30
+ should "remove from the index after deleting" do
31
+ @user2.delete
32
+
33
+ assert_equal [], User.find(:email, "bar").to_a
34
+ end
35
+ end
36
+ end
@@ -241,4 +241,21 @@ class TestRedis < Test::Unit::TestCase
241
241
  assert_equal ["1", "2", "3"], Post[@post.id].comments
242
242
  end
243
243
  end
244
+
245
+ context "Comparison" do
246
+ setup do
247
+ @user = User.create(:email => "foo")
248
+ end
249
+
250
+ should "be comparable to other instances" do
251
+ assert_equal @user, User[@user.id]
252
+
253
+ assert_not_equal @user, User.create
254
+ assert_not_equal User.new, User.new
255
+ end
256
+
257
+ should "not be comparable to instances of other models" do
258
+ assert_not_equal @user, Event.create(:name => "Ruby Tuesday")
259
+ end
260
+ end
244
261
  end
@@ -3,6 +3,10 @@ require File.dirname(__FILE__) + '/test_helper'
3
3
  class ValidationsTest < Test::Unit::TestCase
4
4
  class Event < Ohm::Model
5
5
  attribute :name
6
+ attribute :place
7
+
8
+ index [:name]
9
+ index [:name, :place]
6
10
 
7
11
  def validate
8
12
  assert_format(:name, /^\w+$/)
@@ -40,6 +44,42 @@ class ValidationsTest < Test::Unit::TestCase
40
44
  end
41
45
  end
42
46
  end
47
+
48
+ context "That must have a unique name" do
49
+ should "fail when the value already exists" do
50
+ def @event.validate
51
+ assert_unique [:name]
52
+ end
53
+
54
+ Event.create(:name => "foo")
55
+ @event.name = "foo"
56
+ @event.create
57
+
58
+ assert_nil @event.id
59
+ assert_equal [[[:name], :not_unique]], @event.errors
60
+ end
61
+ end
62
+
63
+ context "That must have a unique name scoped by place" do
64
+ should "fail when the value already exists" do
65
+ def @event.validate
66
+ assert_unique [:name, :place]
67
+ end
68
+
69
+ Event.create(:name => "foo", :place => "bar")
70
+ @event.name = "foo"
71
+ @event.place = "bar"
72
+ @event.create
73
+
74
+ assert_nil @event.id
75
+ assert_equal [[[:name, :place], :not_unique]], @event.errors
76
+
77
+ @event.place = "baz"
78
+ @event.create
79
+
80
+ assert @event.valid?
81
+ end
82
+ end
43
83
  end
44
84
 
45
85
  context "An existing model with a valid name" do
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.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michel Martens, Damian Janowski
@@ -31,6 +31,7 @@ extensions: []
31
31
  extra_rdoc_files: []
32
32
 
33
33
  files:
34
+ - lib/ohm/validations.rb
34
35
  - lib/ohm.rb
35
36
  - README.markdown
36
37
  - LICENSE
@@ -39,15 +40,13 @@ files:
39
40
  - test/benchmarks.rb
40
41
  - test/db/dump.rdb
41
42
  - test/db/redis.pid
43
+ - test/indices_test.rb
42
44
  - test/model_test.rb
43
- - test/references_test.rb
44
45
  - test/test.conf
45
46
  - test/test_helper.rb
46
47
  - test/validations_test.rb
47
48
  has_rdoc: false
48
49
  homepage: http://github.com/soveran/ohm
49
- licenses: []
50
-
51
50
  post_install_message:
52
51
  rdoc_options: []
53
52
 
@@ -68,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
67
  requirements: []
69
68
 
70
69
  rubyforge_project:
71
- rubygems_version: 1.3.2
70
+ rubygems_version: 1.3.1
72
71
  signing_key:
73
72
  specification_version: 2
74
73
  summary: Object-hash mapping library for Redis.
@@ -1,29 +0,0 @@
1
- require File.dirname(__FILE__) + '/test_helper'
2
-
3
- module Ohm
4
- class Reference
5
- attr :model, :id
6
-
7
- def initialize(model, id)
8
- end
9
-
10
- def self.marshal_load(*args)
11
- 1
12
- end
13
- end
14
- end
15
-
16
- class ReferencesTest < Test::Unit::TestCase
17
- class Referee < Ohm::Model
18
- end
19
-
20
- test "dump" do
21
- model = Referee.new.create
22
-
23
- ref = Ohm::Reference.new(Referee, 1)
24
-
25
- $redis["foo"] = ref
26
-
27
- #assert_equal Referee[1], $redis["foo"]
28
- end
29
- end