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 +2 -13
- data/lib/ohm.rb +54 -97
- data/lib/ohm/key.rb +4 -31
- data/lib/ohm/version.rb +1 -1
- data/test/1.8.6_test.rb +18 -18
- data/test/associations_test.rb +100 -0
- data/test/connection_test.rb +33 -21
- data/test/errors_test.rb +88 -88
- data/test/hash_key_test.rb +16 -23
- data/test/helper.rb +25 -0
- data/test/indices_test.rb +177 -183
- data/test/json_test.rb +67 -0
- data/test/model_test.rb +675 -868
- data/test/mutex_test.rb +65 -70
- data/test/pattern_test.rb +8 -6
- data/test/upgrade_script_test.rb +41 -47
- data/test/validations_test.rb +180 -164
- data/test/wrapper_test.rb +7 -5
- metadata +26 -10
- data/test/all_tests.rb +0 -2
- data/test/test_helper.rb +0 -53
data/test/mutex_test.rb
CHANGED
@@ -1,89 +1,84 @@
|
|
1
|
-
|
1
|
+
# encoding: UTF-8
|
2
2
|
|
3
|
-
|
4
|
-
class Person < Ohm::Model
|
5
|
-
attribute :name
|
6
|
-
end
|
7
|
-
|
8
|
-
setup do
|
9
|
-
Ohm.flush
|
10
|
-
@p1 = Person.create :name => "Albert"
|
11
|
-
@p2 = Person[1]
|
12
|
-
end
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
13
4
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
p1 = Thread.new do
|
18
|
-
@p1.mutex do
|
19
|
-
sleep 0.4
|
20
|
-
t1 = Time.now
|
21
|
-
end
|
22
|
-
end
|
5
|
+
class Person < Ohm::Model
|
6
|
+
attribute :name
|
7
|
+
end
|
23
8
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|
9
|
+
setup do
|
10
|
+
@p1 = Person.create :name => "Albert"
|
11
|
+
@p2 = Person[1]
|
12
|
+
end
|
30
13
|
|
31
|
-
|
32
|
-
|
33
|
-
|
14
|
+
test "prevent other instances of the same object from grabing a locked record" do
|
15
|
+
t1 = t2 = nil
|
16
|
+
p1 = Thread.new do
|
17
|
+
@p1.mutex do
|
18
|
+
sleep 0.01
|
19
|
+
t1 = Time.now
|
34
20
|
end
|
21
|
+
end
|
35
22
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
23
|
+
p2 = Thread.new do
|
24
|
+
sleep 0.01
|
25
|
+
@p2.mutex do
|
26
|
+
t2 = Time.now
|
41
27
|
end
|
28
|
+
end
|
42
29
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
30
|
+
p1.join
|
31
|
+
p2.join
|
32
|
+
assert t2 > t1
|
33
|
+
end
|
47
34
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
p2.join
|
55
|
-
p3.join
|
56
|
-
p4.join
|
57
|
-
end
|
58
|
-
end
|
35
|
+
test "allow an instance to lock a record if the previous lock is expired" do
|
36
|
+
@p1.send(:lock!)
|
37
|
+
@p2.mutex do
|
38
|
+
assert true
|
39
|
+
end
|
40
|
+
end
|
59
41
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
42
|
+
test "work if two clients are fighting for the lock" do
|
43
|
+
@p1.send(:lock!)
|
44
|
+
@p3 = Person[1]
|
45
|
+
@p4 = Person[1]
|
65
46
|
|
66
|
-
|
67
|
-
|
47
|
+
p1 = Thread.new { @p1.mutex {} }
|
48
|
+
p2 = Thread.new { @p2.mutex {} }
|
49
|
+
p3 = Thread.new { @p3.mutex {} }
|
50
|
+
p4 = Thread.new { @p4.mutex {} }
|
51
|
+
p1.join
|
52
|
+
p2.join
|
53
|
+
p3.join
|
54
|
+
p4.join
|
55
|
+
end
|
68
56
|
|
69
|
-
|
57
|
+
test "yield the right result after a lock fight" do
|
58
|
+
class Candidate < Ohm::Model
|
59
|
+
attribute :name
|
60
|
+
counter :votes
|
61
|
+
end
|
62
|
+
|
63
|
+
@candidate = Candidate.create :name => "Foo"
|
64
|
+
@candidate.send(:lock!)
|
70
65
|
|
71
|
-
|
72
|
-
m = 2
|
66
|
+
threads = []
|
73
67
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
68
|
+
n = 3
|
69
|
+
m = 2
|
70
|
+
|
71
|
+
n.times do
|
72
|
+
threads << Thread.new do
|
73
|
+
m.times do
|
74
|
+
@candidate.mutex do
|
75
|
+
sleep 0.01
|
76
|
+
@candidate.incr(:votes)
|
82
77
|
end
|
83
78
|
end
|
84
|
-
|
85
|
-
threads.each { |t| t.join }
|
86
|
-
assert_equal n * m, @candidate.votes
|
87
79
|
end
|
88
80
|
end
|
81
|
+
|
82
|
+
threads.each { |t| t.join }
|
83
|
+
assert n * m == @candidate.votes
|
89
84
|
end
|
data/test/pattern_test.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
# encoding: UTF-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
+
|
5
|
+
prepare.clear
|
6
|
+
|
7
|
+
test "should provide pattern matching" do
|
8
|
+
assert(Ohm::Pattern[1, Fixnum] === [1, 2])
|
9
|
+
assert(Ohm::Pattern[String, Array] === ["foo", ["bar"]])
|
8
10
|
end
|
data/test/upgrade_script_test.rb
CHANGED
@@ -1,68 +1,62 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
|
-
require File.expand_path(File.
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
4
|
|
5
5
|
require "ohm/utils/upgrade"
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
# counter :views
|
12
|
-
#
|
13
|
-
# index :email
|
14
|
-
#
|
15
|
-
# set :posts
|
16
|
-
# list :comments
|
17
|
-
# end
|
18
|
-
|
19
|
-
class UpgradeScriptTest < Test::Unit::TestCase
|
20
|
-
def redis
|
21
|
-
Ohm.redis
|
22
|
-
end
|
7
|
+
def redis
|
8
|
+
Ohm.redis
|
9
|
+
end
|
23
10
|
|
24
|
-
|
25
|
-
|
11
|
+
setup do
|
12
|
+
redis.flushdb
|
26
13
|
|
27
|
-
|
14
|
+
@users = Ohm::Key.new(:User, Ohm.redis)
|
28
15
|
|
29
|
-
|
30
|
-
|
31
|
-
|
16
|
+
10.times do
|
17
|
+
@id = redis.incr(@users[:id])
|
18
|
+
@user = @users[@id]
|
32
19
|
|
33
|
-
|
20
|
+
redis.sadd @users[:all], @id
|
34
21
|
|
35
|
-
|
36
|
-
|
37
|
-
|
22
|
+
redis.set @user[:name], "Albert"
|
23
|
+
redis.set @user[:email], "albert-#{@id}@example.com"
|
24
|
+
redis.incr @user[:views]
|
38
25
|
|
39
|
-
|
40
|
-
|
26
|
+
redis.sadd @user[:posts], 1
|
27
|
+
redis.sadd @user[:posts], 2
|
41
28
|
|
42
|
-
|
43
|
-
|
29
|
+
redis.lpush @user[:comments], 3
|
30
|
+
redis.lpush @user[:comments], 4
|
44
31
|
|
45
|
-
|
46
|
-
|
47
|
-
end
|
32
|
+
redis.sadd @user[:_indices], @users[:email][Ohm::Model.encode "albert-#{@id}@example.com"]
|
33
|
+
redis.sadd @users[:email][Ohm::Model.encode "albert-#{@id}@example.com"], @id
|
48
34
|
end
|
35
|
+
end
|
49
36
|
|
50
|
-
|
51
|
-
|
37
|
+
test "upgrade to hashes" do
|
38
|
+
require "stringio"
|
52
39
|
|
53
|
-
|
40
|
+
stderr, stdout = $stderr, $stdout
|
54
41
|
|
55
|
-
|
56
|
-
assert_nil redis.get(@user[:email])
|
57
|
-
assert_nil redis.get(@user[:views])
|
42
|
+
$stderr, $stdout = StringIO.new, StringIO.new
|
58
43
|
|
59
|
-
|
44
|
+
Ohm::Utils::Upgrade.new([:User]).run
|
60
45
|
|
61
|
-
|
62
|
-
assert_equal ["1"], redis.smembers(@users[:email][Ohm::Model.encode "albert-1@example.com"])
|
46
|
+
$stderr, $stdout = stderr, stdout
|
63
47
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
48
|
+
@user = @users[1]
|
49
|
+
|
50
|
+
assert redis.get(@user[:name]).nil?
|
51
|
+
assert redis.get(@user[:email]).nil?
|
52
|
+
assert redis.get(@user[:views]).nil?
|
53
|
+
|
54
|
+
assert ["1", "2"] == redis.smembers(@user[:posts])
|
55
|
+
|
56
|
+
assert [@users[:email][Ohm::Model.encode "albert-1@example.com"]] == redis.smembers(@user[:_indices])
|
57
|
+
assert ["1"] == redis.smembers(@users[:email][Ohm::Model.encode "albert-1@example.com"])
|
58
|
+
|
59
|
+
assert "Albert" == redis.hget(@user, :name)
|
60
|
+
assert "albert-1@example.com" == redis.hget(@user, :email)
|
61
|
+
assert "1" == redis.hget(@user, :views)
|
68
62
|
end
|
data/test/validations_test.rb
CHANGED
@@ -1,229 +1,245 @@
|
|
1
|
-
|
1
|
+
# encoding: UTF-8
|
2
2
|
|
3
|
-
|
4
|
-
class Event < Ohm::Model
|
5
|
-
attribute :name
|
6
|
-
attribute :place
|
7
|
-
attribute :capacity
|
3
|
+
require File.expand_path("./helper", File.dirname(__FILE__))
|
8
4
|
|
9
|
-
|
10
|
-
|
5
|
+
class Event < Ohm::Model
|
6
|
+
attribute :name
|
7
|
+
attribute :place
|
8
|
+
attribute :capacity
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
index :name
|
11
|
+
index :place
|
12
|
+
|
13
|
+
def validate
|
14
|
+
assert_format(:name, /^\w+$/)
|
15
15
|
end
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
@event = Event.new
|
20
|
-
end
|
18
|
+
class Validatable
|
19
|
+
attr_accessor :name
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
@event.create
|
25
|
-
assert @event.new?
|
26
|
-
end
|
21
|
+
include Ohm::Validations
|
22
|
+
end
|
27
23
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
# A new model with validations
|
25
|
+
scope do
|
26
|
+
setup do
|
27
|
+
Event.new
|
28
|
+
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
30
|
+
# That must have a present name
|
31
|
+
scope do
|
32
|
+
test "not be created if the name is never assigned" do |event|
|
33
|
+
event.create
|
34
|
+
assert event.new?
|
35
|
+
end
|
39
36
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
assert @event.new?
|
45
|
-
end
|
46
|
-
end
|
37
|
+
test "not be created if the name assigned is empty" do |event|
|
38
|
+
event.name = ""
|
39
|
+
event.create
|
40
|
+
assert event.new?
|
47
41
|
end
|
48
42
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
43
|
+
test "be created if the name assigned is not empty" do |event|
|
44
|
+
event.name = "hello"
|
45
|
+
event.create
|
46
|
+
assert event.id
|
47
|
+
end
|
54
48
|
|
55
|
-
|
56
|
-
|
57
|
-
|
49
|
+
# And must have a name with only \w+
|
50
|
+
scope do
|
51
|
+
test "not be created if the name doesn't match /^\w+$/" do |event|
|
52
|
+
event.name = "hello-world"
|
53
|
+
event.create
|
54
|
+
assert event.new?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
58
|
|
59
|
-
|
60
|
-
|
59
|
+
# That must have a numeric attribute :capacity
|
60
|
+
scope do
|
61
|
+
test "fail when the value is nil" do |event|
|
62
|
+
def event.validate
|
63
|
+
assert_numeric :capacity
|
61
64
|
end
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
66
|
+
event.name = "foo"
|
67
|
+
event.place = "bar"
|
68
|
+
event.create
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
@event.create
|
70
|
+
assert event.new?
|
71
|
+
assert [[:capacity, :not_numeric]] == event.errors
|
72
|
+
end
|
72
73
|
|
73
|
-
|
74
|
-
|
74
|
+
test "fail when the value is not numeric" do |event|
|
75
|
+
def event.validate
|
76
|
+
assert_numeric :capacity
|
75
77
|
end
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
event.name = "foo"
|
80
|
+
event.place = "bar"
|
81
|
+
event.capacity = "baz"
|
82
|
+
event.create
|
81
83
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
@event.create
|
84
|
+
assert event.new?
|
85
|
+
assert [[:capacity, :not_numeric]] == event.errors
|
86
|
+
end
|
86
87
|
|
87
|
-
|
88
|
+
test "succeed when the value is numeric" do |event|
|
89
|
+
def event.validate
|
90
|
+
assert_numeric :capacity
|
88
91
|
end
|
89
|
-
end
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
93
|
+
event.name = "foo"
|
94
|
+
event.place = "bar"
|
95
|
+
event.capacity = 42
|
96
|
+
event.create
|
96
97
|
|
97
|
-
|
98
|
-
|
99
|
-
|
98
|
+
assert event.id
|
99
|
+
end
|
100
|
+
end
|
100
101
|
|
101
|
-
|
102
|
-
|
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
|
103
107
|
end
|
104
|
-
end
|
105
108
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
assert_unique [:name, :place]
|
110
|
-
end
|
109
|
+
Event.create(:name => "foo")
|
110
|
+
event.name = "foo"
|
111
|
+
event.create
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
assert event.new?
|
114
|
+
assert [[:name, :not_unique]] == event.errors
|
115
|
+
end
|
116
|
+
end
|
116
117
|
|
117
|
-
|
118
|
-
|
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
|
119
124
|
|
120
|
-
|
121
|
-
|
125
|
+
Event.create(:name => "foo", :place => "bar")
|
126
|
+
event.name = "foo"
|
127
|
+
event.place = "bar"
|
128
|
+
event.create
|
122
129
|
|
123
|
-
|
124
|
-
|
125
|
-
end
|
130
|
+
assert event.new?
|
131
|
+
assert [[[:name, :place], :not_unique]] == event.errors
|
126
132
|
|
127
|
-
|
128
|
-
|
129
|
-
def @event.validate
|
130
|
-
assert_unique :capacity
|
131
|
-
end
|
133
|
+
event.place = "baz"
|
134
|
+
event.create
|
132
135
|
|
133
|
-
|
134
|
-
@event.valid?
|
135
|
-
end
|
136
|
-
end
|
136
|
+
assert event.valid?
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
context "That has the name changed" do
|
146
|
-
should "not be saved if the new name is nil" do
|
147
|
-
@event.name = nil
|
148
|
-
@event.save
|
149
|
-
assert_equal false, @event.valid?
|
150
|
-
assert_equal "original", Event[@event.id].name
|
151
|
-
end
|
152
|
-
|
153
|
-
should "not be saved if the name assigned is empty" do
|
154
|
-
@event.name = ""
|
155
|
-
@event.save
|
156
|
-
assert_equal false, @event.valid?
|
157
|
-
assert_equal "original", Event[@event.id].name
|
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
|
158
145
|
end
|
159
146
|
|
160
|
-
|
161
|
-
|
162
|
-
@event.save
|
163
|
-
assert @event.valid?
|
164
|
-
assert_equal "hello", Event[@event.id].name
|
147
|
+
assert_raise(Ohm::Model::IndexNotFound) do
|
148
|
+
event.valid?
|
165
149
|
end
|
166
150
|
end
|
167
151
|
end
|
152
|
+
end
|
168
153
|
|
169
|
-
|
170
|
-
|
171
|
-
|
154
|
+
# An existing model with a valid name
|
155
|
+
scope do
|
156
|
+
setup do
|
157
|
+
event = Event.create(:name => "original")
|
158
|
+
end
|
172
159
|
|
173
|
-
|
160
|
+
# That has the name changed
|
161
|
+
scope do
|
162
|
+
test "not be saved if the new name is nil" do |event|
|
163
|
+
event.name = nil
|
164
|
+
event.save
|
165
|
+
assert false == event.valid?
|
166
|
+
assert "original" == Event[event.id].name
|
174
167
|
end
|
175
168
|
|
176
|
-
|
177
|
-
|
169
|
+
test "not be saved if the name assigned is empty" do |event|
|
170
|
+
event.name = ""
|
171
|
+
event.save
|
172
|
+
assert false == event.valid?
|
173
|
+
assert "original" == Event[event.id].name
|
178
174
|
end
|
179
175
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
176
|
+
test "be saved if the name assigned is not empty" do |event|
|
177
|
+
event.name = "hello"
|
178
|
+
event.save
|
179
|
+
assert event.valid?
|
180
|
+
assert "hello" == Event[event.id].name
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
185
184
|
|
186
|
-
|
185
|
+
# Validations module
|
186
|
+
scope do
|
187
|
+
setup do
|
188
|
+
Validatable.new
|
189
|
+
end
|
187
190
|
|
188
|
-
|
191
|
+
# assert
|
192
|
+
scope do
|
193
|
+
test "add errors to a collection" do |target|
|
194
|
+
def target.validate
|
195
|
+
assert(false, "Something bad")
|
189
196
|
end
|
190
197
|
|
191
|
-
|
192
|
-
def @target.validate
|
193
|
-
if assert(true, "No error")
|
194
|
-
assert(false, "Chained error")
|
195
|
-
end
|
198
|
+
target.validate
|
196
199
|
|
197
|
-
|
198
|
-
|
199
|
-
end
|
200
|
-
end
|
200
|
+
assert ["Something bad"] == target.errors
|
201
|
+
end
|
201
202
|
|
202
|
-
|
203
|
+
test "allow for nested validations" do |target|
|
204
|
+
def target.validate
|
205
|
+
if assert(true, "No error")
|
206
|
+
assert(false, "Chained error")
|
207
|
+
end
|
203
208
|
|
204
|
-
|
209
|
+
if assert(false, "Parent error")
|
210
|
+
assert(false, "No chained error")
|
211
|
+
end
|
205
212
|
end
|
213
|
+
|
214
|
+
target.validate
|
215
|
+
|
216
|
+
assert ["Chained error", "Parent error"] == target.errors
|
206
217
|
end
|
218
|
+
end
|
207
219
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
220
|
+
# assert_present
|
221
|
+
scope do
|
222
|
+
setup do
|
223
|
+
target = Validatable.new
|
224
|
+
|
225
|
+
def target.validate
|
226
|
+
assert_present(:name)
|
213
227
|
end
|
214
228
|
|
215
|
-
|
216
|
-
|
229
|
+
target
|
230
|
+
end
|
231
|
+
|
232
|
+
test "fail when the attribute is nil" do |target|
|
233
|
+
target.validate
|
217
234
|
|
218
|
-
|
219
|
-
|
235
|
+
assert [[:name, :not_present]] == target.errors
|
236
|
+
end
|
220
237
|
|
221
|
-
|
222
|
-
|
223
|
-
|
238
|
+
test "fail when the attribute is empty" do |target|
|
239
|
+
target.name = ""
|
240
|
+
target.validate
|
224
241
|
|
225
|
-
|
226
|
-
end
|
242
|
+
assert [[:name, :not_present]] == target.errors
|
227
243
|
end
|
228
244
|
end
|
229
245
|
end
|