ohm 0.1.5 → 1.0.0.alpha1
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 +19 -1
- data/lib/ohm.rb +893 -1662
- data/lib/ohm/transaction.rb +129 -0
- data/test/association.rb +33 -0
- data/test/connection.rb +72 -0
- data/test/core.rb +26 -0
- data/test/counters.rb +67 -0
- data/test/extensibility.rb +48 -0
- data/test/filtering.rb +42 -0
- data/test/{hash_key_test.rb → hash_key.rb} +0 -0
- data/test/indices.rb +97 -0
- data/test/{json_test.rb → json.rb} +10 -3
- data/test/lua-save.rb +193 -0
- data/test/lua.rb +47 -0
- data/test/{model_test.rb → model.rb} +325 -439
- data/test/pipeline-performance.rb +65 -0
- data/test/transactions.rb +241 -0
- data/test/uniques.rb +87 -0
- data/test/{associations_test.rb → unused/associations_test.rb} +3 -3
- data/test/{pattern_test.rb → unused/pattern_test.rb} +0 -0
- data/test/{upgrade_script_test.rb → unused/upgrade_script_test.rb} +0 -0
- data/test/{wrapper_test.rb → unused/wrapper_test.rb} +0 -0
- data/test/{validations_test.rb → validations.rb} +20 -70
- metadata +44 -29
- data/lib/ohm/compat-1.8.6.rb +0 -39
- data/lib/ohm/key.rb +0 -35
- data/lib/ohm/pattern.rb +0 -37
- data/lib/ohm/validations.rb +0 -213
- data/lib/ohm/version.rb +0 -5
- data/test/connection_test.rb +0 -101
- data/test/errors_test.rb +0 -120
- data/test/indices_test.rb +0 -213
- data/test/mutex_test.rb +0 -84
@@ -0,0 +1,65 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
Ohm.flush
|
4
|
+
|
5
|
+
class User < Ohm::Model
|
6
|
+
attribute :fname
|
7
|
+
attribute :lname
|
8
|
+
attribute :bday
|
9
|
+
attribute :gender
|
10
|
+
attribute :city
|
11
|
+
attribute :state
|
12
|
+
attribute :country
|
13
|
+
attribute :zip
|
14
|
+
end
|
15
|
+
|
16
|
+
create = lambda do |i|
|
17
|
+
User.new(fname: "John#{i}",
|
18
|
+
lname: "Doe#{i}",
|
19
|
+
bday: Time.now.to_s,
|
20
|
+
gender: "Male",
|
21
|
+
city: "Los Angeles",
|
22
|
+
state: "CA",
|
23
|
+
country: "US",
|
24
|
+
zip: "90210").save
|
25
|
+
end
|
26
|
+
|
27
|
+
10.times(&create)
|
28
|
+
|
29
|
+
require "benchmark"
|
30
|
+
|
31
|
+
t1 = Benchmark.realtime do
|
32
|
+
User.all.sort_by(:fname, order: "DESC ALPHA").each do |user|
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
t2 = Benchmark.realtime do
|
37
|
+
ids = User.key[:all].smembers
|
38
|
+
|
39
|
+
ids.each do |id|
|
40
|
+
User[id]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
test "pipelined approach should be 1.5 at least times faster for 10 records" do
|
45
|
+
assert(t2 / t1 >= 1.5)
|
46
|
+
end
|
47
|
+
|
48
|
+
90.times(&create)
|
49
|
+
|
50
|
+
t1 = Benchmark.realtime do
|
51
|
+
User.all.sort_by(:fname, order: "DESC ALPHA").each do |user|
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
t2 = Benchmark.realtime do
|
56
|
+
ids = User.key[:all].smembers
|
57
|
+
|
58
|
+
ids.each do |id|
|
59
|
+
User[id]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
test "the pipelined approach should be 2 times faster for 100 records" do
|
64
|
+
assert(t2 / t1 >= 2)
|
65
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
+
require "ohm/transaction"
|
5
|
+
|
6
|
+
prepare do
|
7
|
+
Ohm.redis.del("foo")
|
8
|
+
end
|
9
|
+
|
10
|
+
setup do
|
11
|
+
Ohm.redis
|
12
|
+
end
|
13
|
+
|
14
|
+
test "basic functionality" do |db|
|
15
|
+
t = Ohm::Transaction.new
|
16
|
+
x = nil
|
17
|
+
|
18
|
+
t.watch("foo")
|
19
|
+
|
20
|
+
t.read do
|
21
|
+
x = db.get("foo")
|
22
|
+
end
|
23
|
+
|
24
|
+
t.write do
|
25
|
+
db.set("foo", x.to_i + 2)
|
26
|
+
end
|
27
|
+
|
28
|
+
t.commit(db)
|
29
|
+
|
30
|
+
assert_equal "2", db.get("foo")
|
31
|
+
end
|
32
|
+
|
33
|
+
test "new returns a transaction" do |db|
|
34
|
+
t1 = Ohm::Transaction.new do |t|
|
35
|
+
t.write do
|
36
|
+
db.set("foo", "bar")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
t1.commit(db)
|
41
|
+
|
42
|
+
assert_equal "bar", db.get("foo")
|
43
|
+
end
|
44
|
+
|
45
|
+
test "transaction local storage" do |db|
|
46
|
+
t1 = Ohm::Transaction.new do |t|
|
47
|
+
t.read do |s|
|
48
|
+
s.foo = db.type("foo")
|
49
|
+
end
|
50
|
+
|
51
|
+
t.write do |s|
|
52
|
+
db.set("foo", s.foo.reverse)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
t1.commit(db)
|
57
|
+
|
58
|
+
assert_equal "enon", db.get("foo")
|
59
|
+
end
|
60
|
+
|
61
|
+
test "composed transaction" do |db|
|
62
|
+
t1 = Ohm::Transaction.new do |t|
|
63
|
+
t.watch("foo")
|
64
|
+
|
65
|
+
t.write do |s|
|
66
|
+
db.set("foo", "bar")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
t2 = Ohm::Transaction.new do |t|
|
71
|
+
t.watch("foo")
|
72
|
+
|
73
|
+
t.write do |s|
|
74
|
+
db.set("foo", "baz")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
t3 = Ohm::Transaction.new
|
79
|
+
t3.append(t1)
|
80
|
+
t3.append(t2)
|
81
|
+
t3.commit(db)
|
82
|
+
|
83
|
+
assert_equal "baz", db.get("foo")
|
84
|
+
|
85
|
+
t4 = Ohm::Transaction.new
|
86
|
+
t4.append(t2)
|
87
|
+
t4.append(t1)
|
88
|
+
t4.commit(db)
|
89
|
+
|
90
|
+
assert_equal "bar", db.get("foo")
|
91
|
+
|
92
|
+
t5 = Ohm::Transaction.new
|
93
|
+
t5.append(t4)
|
94
|
+
t5.commit(db)
|
95
|
+
|
96
|
+
assert_equal "bar", db.get("foo")
|
97
|
+
|
98
|
+
assert_equal Set.new(["foo"]), t5.phase[:watch]
|
99
|
+
assert_equal 2, t5.phase[:write].size
|
100
|
+
end
|
101
|
+
|
102
|
+
test "composing transactions with append" do |db|
|
103
|
+
t1 = Ohm::Transaction.new do |t|
|
104
|
+
t.write do
|
105
|
+
db.set("foo", "bar")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
t2 = Ohm::Transaction.new do |t|
|
110
|
+
t.write do
|
111
|
+
db.set("foo", "baz")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
t1.append(t2)
|
116
|
+
t1.commit(db)
|
117
|
+
|
118
|
+
assert_equal "baz", db.get("foo")
|
119
|
+
|
120
|
+
t2.append(t1)
|
121
|
+
t2.commit(db)
|
122
|
+
|
123
|
+
assert_equal "bar", db.get("foo")
|
124
|
+
end
|
125
|
+
|
126
|
+
test "appending or prepending is determined by when append is called" do |db|
|
127
|
+
t1 = Ohm::Transaction.new do |t|
|
128
|
+
t.write do
|
129
|
+
db.set("foo", "bar")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
t2 = Ohm::Transaction.new do |t|
|
134
|
+
t.append(t1)
|
135
|
+
|
136
|
+
t.write do
|
137
|
+
db.set("foo", "baz")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
t3 = Ohm::Transaction.new do |t|
|
142
|
+
t.write do
|
143
|
+
db.set("foo", "baz")
|
144
|
+
end
|
145
|
+
|
146
|
+
t.append(t1)
|
147
|
+
end
|
148
|
+
|
149
|
+
t2.commit(db)
|
150
|
+
|
151
|
+
assert_equal "baz", db.get("foo")
|
152
|
+
|
153
|
+
t3.commit(db)
|
154
|
+
|
155
|
+
assert_equal "bar", db.get("foo")
|
156
|
+
end
|
157
|
+
|
158
|
+
test "storage in composed transactions" do |db|
|
159
|
+
t1 = Ohm::Transaction.new do |t|
|
160
|
+
t.read do |s|
|
161
|
+
s.foo = db.type("foo")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
t2 = Ohm::Transaction.new do |t|
|
166
|
+
t.write do |s|
|
167
|
+
db.set("foo", s.foo.reverse)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
t1.append(t2).commit(db)
|
172
|
+
|
173
|
+
assert_equal "enon", db.get("foo")
|
174
|
+
end
|
175
|
+
|
176
|
+
test "storage entries can't be overriden" do |db|
|
177
|
+
t1 = Ohm::Transaction.new do |t|
|
178
|
+
t.read do |s|
|
179
|
+
s.foo = db.type("foo")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
t2 = Ohm::Transaction.new do |t|
|
184
|
+
t.read do |s|
|
185
|
+
s.foo = db.exists("foo")
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
assert_raise Ohm::Transaction::Store::EntryAlreadyExistsError do
|
190
|
+
t1.append(t2).commit(db)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
__END__
|
195
|
+
# We leave this here to indicate what the past behavior was with
|
196
|
+
# model transactions.
|
197
|
+
|
198
|
+
class Post < Ohm::Model
|
199
|
+
attribute :body
|
200
|
+
attribute :state
|
201
|
+
index :state
|
202
|
+
|
203
|
+
def before_save
|
204
|
+
self.body = body.to_s.strip
|
205
|
+
end
|
206
|
+
|
207
|
+
def before_create
|
208
|
+
self.state = "draft"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
test "transactions in models" do |db|
|
213
|
+
p = Post.new(body: " foo ")
|
214
|
+
|
215
|
+
db.set "csv:foo", "A,B"
|
216
|
+
|
217
|
+
t1 = Ohm::Transaction.define do |t|
|
218
|
+
t.watch("csv:foo")
|
219
|
+
|
220
|
+
t.read do |s|
|
221
|
+
s.csv = db.get("csv:foo")
|
222
|
+
end
|
223
|
+
|
224
|
+
t.write do |s|
|
225
|
+
db.set("csv:foo", s.csv + "," + "C")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
main = Ohm::Transaction.new(p.transaction_for_create, t1)
|
230
|
+
main.commit(db)
|
231
|
+
|
232
|
+
# Verify the Post transaction proceeded without a hitch
|
233
|
+
p = Post[p.id]
|
234
|
+
|
235
|
+
assert_equal "draft", p.state
|
236
|
+
assert_equal "foo", p.body
|
237
|
+
assert Post.find(state: "draft").include?(p)
|
238
|
+
|
239
|
+
# Verify that the second transaction happened
|
240
|
+
assert_equal "A,B,C", db.get("csv:foo")
|
241
|
+
end
|
data/test/uniques.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
class User < Ohm::Model
|
6
|
+
attribute :email
|
7
|
+
unique :email
|
8
|
+
unique :provider
|
9
|
+
|
10
|
+
def self.[](id)
|
11
|
+
super(id.to_i)
|
12
|
+
end
|
13
|
+
|
14
|
+
def provider
|
15
|
+
email[/@(.*?).com/, 1]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
setup do
|
20
|
+
User.create(email: "a@a.com")
|
21
|
+
end
|
22
|
+
|
23
|
+
test "findability" do |u|
|
24
|
+
assert_equal u, User.with(:email, "a@a.com")
|
25
|
+
end
|
26
|
+
|
27
|
+
test "raises when it already exists during create" do
|
28
|
+
assert_raise Ohm::UniqueIndexViolation do
|
29
|
+
User.create(email: "a@a.com")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
test "raises when it already exists during save" do
|
34
|
+
u = User.create(email: "b@b.com")
|
35
|
+
u.email = "a@a.com"
|
36
|
+
|
37
|
+
assert_raise Ohm::UniqueIndexViolation do
|
38
|
+
u.save
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
test "doesn't raise when saving again and again" do |u|
|
43
|
+
ex = nil
|
44
|
+
|
45
|
+
begin
|
46
|
+
User[u.id].save
|
47
|
+
rescue Exception => e
|
48
|
+
ex = e
|
49
|
+
end
|
50
|
+
|
51
|
+
assert_equal nil, ex
|
52
|
+
end
|
53
|
+
|
54
|
+
test "removes the previous index when changing" do
|
55
|
+
u = User.create(email: "c@c.com")
|
56
|
+
u.update(email: "d@d.com")
|
57
|
+
|
58
|
+
assert_equal nil, User.with(:email, "c@c.com")
|
59
|
+
assert_equal nil, User.key[:unique][:email].hget("c@c.com")
|
60
|
+
assert_equal u, User.with(:email, "d@d.com")
|
61
|
+
end
|
62
|
+
|
63
|
+
test "removes the previous index when deleting" do |u|
|
64
|
+
u.delete
|
65
|
+
|
66
|
+
assert_equal nil, User.with(:email, "a@a.com")
|
67
|
+
assert_equal nil, User.key[:unique][:email].hget("a@a.com")
|
68
|
+
end
|
69
|
+
|
70
|
+
test "unique virtual attribute" do
|
71
|
+
u = User.create(email: "foo@yahoo.com")
|
72
|
+
|
73
|
+
assert_equal u, User.with(:provider, "yahoo")
|
74
|
+
|
75
|
+
# Yahoo should be allowed because this user is the one reserved for it.
|
76
|
+
u.update(email: "bar@yahoo.com")
|
77
|
+
|
78
|
+
# `a` is not allowed though.
|
79
|
+
assert_raise Ohm::UniqueIndexViolation do
|
80
|
+
u.update(email: "bar@a.com")
|
81
|
+
end
|
82
|
+
|
83
|
+
# And so is yahoo if we try creating a different user.
|
84
|
+
assert_raise Ohm::UniqueIndexViolation do
|
85
|
+
User.create(email: "baz@yahoo.com")
|
86
|
+
end
|
87
|
+
end
|
@@ -63,12 +63,12 @@ test "be cached in an instance variable" do
|
|
63
63
|
|
64
64
|
@post.update(:author => Person.create(:name => "Bertrand"))
|
65
65
|
|
66
|
-
|
67
|
-
|
66
|
+
assert_equal "Bertrand", @post.author.name
|
67
|
+
assert_equal @post.author.object_id, @post.author.object_id
|
68
68
|
|
69
69
|
@post.update(:author_id => Person.create(:name => "Charles").id)
|
70
70
|
|
71
|
-
|
71
|
+
assert_equal "Charles", @post.author.name
|
72
72
|
end
|
73
73
|
|
74
74
|
setup do
|
File without changes
|
File without changes
|
File without changes
|
@@ -18,7 +18,7 @@ end
|
|
18
18
|
class Validatable
|
19
19
|
attr_accessor :name
|
20
20
|
|
21
|
-
include
|
21
|
+
include Scrivener::Validations
|
22
22
|
end
|
23
23
|
|
24
24
|
# A new model with validations
|
@@ -30,19 +30,19 @@ scope do
|
|
30
30
|
# That must have a present name
|
31
31
|
scope do
|
32
32
|
test "not be created if the name is never assigned" do |event|
|
33
|
-
event.
|
33
|
+
event.save
|
34
34
|
assert event.new?
|
35
35
|
end
|
36
36
|
|
37
37
|
test "not be created if the name assigned is empty" do |event|
|
38
38
|
event.name = ""
|
39
|
-
event.
|
39
|
+
event.save
|
40
40
|
assert event.new?
|
41
41
|
end
|
42
42
|
|
43
43
|
test "be created if the name assigned is not empty" do |event|
|
44
44
|
event.name = "hello"
|
45
|
-
event.
|
45
|
+
event.save
|
46
46
|
assert event.id
|
47
47
|
end
|
48
48
|
|
@@ -50,7 +50,7 @@ scope do
|
|
50
50
|
scope do
|
51
51
|
test "not be created if the name doesn't match /^\w+$/" do |event|
|
52
52
|
event.name = "hello-world"
|
53
|
-
event.
|
53
|
+
event.save
|
54
54
|
assert event.new?
|
55
55
|
end
|
56
56
|
end
|
@@ -65,10 +65,10 @@ scope do
|
|
65
65
|
|
66
66
|
event.name = "foo"
|
67
67
|
event.place = "bar"
|
68
|
-
event.
|
68
|
+
event.save
|
69
69
|
|
70
70
|
assert event.new?
|
71
|
-
|
71
|
+
assert_equal({capacity: [:not_numeric]}, event.errors)
|
72
72
|
end
|
73
73
|
|
74
74
|
test "fail when the value is not numeric" do |event|
|
@@ -79,10 +79,10 @@ scope do
|
|
79
79
|
event.name = "foo"
|
80
80
|
event.place = "bar"
|
81
81
|
event.capacity = "baz"
|
82
|
-
event.
|
82
|
+
event.save
|
83
83
|
|
84
84
|
assert event.new?
|
85
|
-
|
85
|
+
assert_equal({capacity: [:not_numeric]}, event.errors)
|
86
86
|
end
|
87
87
|
|
88
88
|
test "succeed when the value is numeric" do |event|
|
@@ -93,62 +93,11 @@ scope do
|
|
93
93
|
event.name = "foo"
|
94
94
|
event.place = "bar"
|
95
95
|
event.capacity = 42
|
96
|
-
event.
|
96
|
+
event.save
|
97
97
|
|
98
98
|
assert event.id
|
99
99
|
end
|
100
100
|
end
|
101
|
-
|
102
|
-
# That must have a unique name
|
103
|
-
scope do
|
104
|
-
test "fail when the value already exists" do |event|
|
105
|
-
def event.validate
|
106
|
-
assert_unique :name
|
107
|
-
end
|
108
|
-
|
109
|
-
Event.create(:name => "foo")
|
110
|
-
event.name = "foo"
|
111
|
-
event.create
|
112
|
-
|
113
|
-
assert event.new?
|
114
|
-
assert [[:name, :not_unique]] == event.errors
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
# That must have a unique name scoped by place
|
119
|
-
scope do
|
120
|
-
test "fail when the value already exists for a scoped attribute" do |event|
|
121
|
-
def event.validate
|
122
|
-
assert_unique [:name, :place]
|
123
|
-
end
|
124
|
-
|
125
|
-
Event.create(:name => "foo", :place => "bar")
|
126
|
-
event.name = "foo"
|
127
|
-
event.place = "bar"
|
128
|
-
event.create
|
129
|
-
|
130
|
-
assert event.new?
|
131
|
-
assert [[[:name, :place], :not_unique]] == event.errors
|
132
|
-
|
133
|
-
event.place = "baz"
|
134
|
-
event.create
|
135
|
-
|
136
|
-
assert event.valid?
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# That defines a unique validation on a non indexed attribute
|
141
|
-
scope do
|
142
|
-
test "raise ArgumentError" do |event|
|
143
|
-
def event.validate
|
144
|
-
assert_unique :capacity
|
145
|
-
end
|
146
|
-
|
147
|
-
assert_raise(Ohm::Model::IndexNotFound) do
|
148
|
-
event.valid?
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
101
|
end
|
153
102
|
|
154
103
|
# An existing model with a valid name
|
@@ -192,28 +141,29 @@ scope do
|
|
192
141
|
scope do
|
193
142
|
test "add errors to a collection" do |target|
|
194
143
|
def target.validate
|
195
|
-
assert(false, "Something bad")
|
144
|
+
assert(false, ["Attribute", "Something bad"])
|
196
145
|
end
|
197
146
|
|
198
147
|
target.validate
|
199
148
|
|
200
|
-
|
149
|
+
assert_equal({ "Attribute" => ["Something bad"] }, target.errors)
|
201
150
|
end
|
202
151
|
|
203
152
|
test "allow for nested validations" do |target|
|
204
153
|
def target.validate
|
205
|
-
if assert(true, "No error")
|
206
|
-
assert(false, "Chained error")
|
154
|
+
if assert(true, ["Attribute", "No error"])
|
155
|
+
assert(false, ["Attribute", "Chained error"])
|
207
156
|
end
|
208
157
|
|
209
|
-
if assert(false, "Parent error")
|
210
|
-
assert(false, "No chained error")
|
158
|
+
if assert(false, ["Attribute", "Parent error"])
|
159
|
+
assert(false, ["Attribute", "No chained error"])
|
211
160
|
end
|
212
161
|
end
|
213
162
|
|
214
163
|
target.validate
|
215
164
|
|
216
|
-
|
165
|
+
expected = {"Attribute"=>["Chained error", "Parent error"]}
|
166
|
+
assert_equal expected, target.errors
|
217
167
|
end
|
218
168
|
end
|
219
169
|
|
@@ -232,14 +182,14 @@ scope do
|
|
232
182
|
test "fail when the attribute is nil" do |target|
|
233
183
|
target.validate
|
234
184
|
|
235
|
-
|
185
|
+
assert_equal({ name: [:not_present] }, target.errors)
|
236
186
|
end
|
237
187
|
|
238
188
|
test "fail when the attribute is empty" do |target|
|
239
189
|
target.name = ""
|
240
190
|
target.validate
|
241
191
|
|
242
|
-
|
192
|
+
assert_equal({ name: [:not_present] }, target.errors)
|
243
193
|
end
|
244
194
|
end
|
245
195
|
end
|