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
data/lib/ohm/version.rb
DELETED
data/test/connection_test.rb
DELETED
@@ -1,101 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
-
|
5
|
-
prepare.clear
|
6
|
-
|
7
|
-
test "connects lazily" do
|
8
|
-
Ohm.connect(:port => 9876)
|
9
|
-
|
10
|
-
begin
|
11
|
-
Ohm.redis.get "foo"
|
12
|
-
rescue => e
|
13
|
-
assert Errno::ECONNREFUSED == e.class
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
test "provides a separate connection for each thread" do
|
18
|
-
assert Ohm.redis == Ohm.redis
|
19
|
-
|
20
|
-
conn1, conn2 = nil
|
21
|
-
|
22
|
-
threads = []
|
23
|
-
|
24
|
-
threads << Thread.new do
|
25
|
-
conn1 = Ohm.redis
|
26
|
-
end
|
27
|
-
|
28
|
-
threads << Thread.new do
|
29
|
-
conn2 = Ohm.redis
|
30
|
-
end
|
31
|
-
|
32
|
-
threads.each { |t| t.join }
|
33
|
-
|
34
|
-
assert conn1 != conn2
|
35
|
-
end
|
36
|
-
|
37
|
-
test "supports connecting by URL" do
|
38
|
-
Ohm.connect(:url => "redis://localhost:9876")
|
39
|
-
|
40
|
-
begin
|
41
|
-
Ohm.redis.get "foo"
|
42
|
-
rescue => e
|
43
|
-
assert Errno::ECONNREFUSED == e.class
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
test "Model.db is the same as Ohm.redis by default" do
|
48
|
-
class U < Ohm::Model
|
49
|
-
end
|
50
|
-
|
51
|
-
assert_equal U.db.object_id, Ohm.redis.object_id
|
52
|
-
end
|
53
|
-
|
54
|
-
test "provides a unique Model.db connection in one thread" do
|
55
|
-
class U < Ohm::Model
|
56
|
-
end
|
57
|
-
|
58
|
-
U.connect(db: 9876)
|
59
|
-
|
60
|
-
r1 = U.db
|
61
|
-
r2 = U.db
|
62
|
-
|
63
|
-
assert_equal r1.object_id, r2.object_id
|
64
|
-
end
|
65
|
-
|
66
|
-
test "provides distinct Model.db connections per thread" do
|
67
|
-
class U < Ohm::Model
|
68
|
-
end
|
69
|
-
|
70
|
-
U.connect(db: 9876)
|
71
|
-
|
72
|
-
r1 = nil
|
73
|
-
r2 = nil
|
74
|
-
|
75
|
-
Thread.new { r1 = U.db }.join
|
76
|
-
Thread.new { r2 = U.db }.join
|
77
|
-
|
78
|
-
assert r1.object_id != r2.object_id
|
79
|
-
end
|
80
|
-
|
81
|
-
test "busts threaded cache when doing Model.connect" do
|
82
|
-
class U < Ohm::Model
|
83
|
-
end
|
84
|
-
|
85
|
-
U.connect(db: 9876)
|
86
|
-
r1 = U.db
|
87
|
-
|
88
|
-
U.connect(db: 9876)
|
89
|
-
r2 = U.db
|
90
|
-
|
91
|
-
assert r1.object_id != r2.object_id
|
92
|
-
end
|
93
|
-
|
94
|
-
test "disallows the non-thread safe writing of Model.db" do
|
95
|
-
class U < Ohm::Model
|
96
|
-
end
|
97
|
-
|
98
|
-
assert_raise NoMethodError do
|
99
|
-
U.db = Redis.connect
|
100
|
-
end
|
101
|
-
end
|
data/test/errors_test.rb
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
-
|
5
|
-
class User < Ohm::Model
|
6
|
-
attribute :name
|
7
|
-
attribute :account
|
8
|
-
|
9
|
-
def validate
|
10
|
-
assert_present :name
|
11
|
-
assert_present :account
|
12
|
-
assert false, :terrible_error
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
setup do
|
17
|
-
@model = User.new(:account => "")
|
18
|
-
@model.valid?
|
19
|
-
end
|
20
|
-
|
21
|
-
test "raise an error if the errors are not handled" do
|
22
|
-
begin
|
23
|
-
@model.errors.present do |e|
|
24
|
-
e.on :terrible_error do
|
25
|
-
end
|
26
|
-
end
|
27
|
-
rescue => e
|
28
|
-
assert e.class == Ohm::Validations::Presenter::UnhandledErrors
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
test "evaluate blocks when errors match" do
|
33
|
-
values = []
|
34
|
-
|
35
|
-
@model.errors.present do |e|
|
36
|
-
e.on [:name, :not_present] do
|
37
|
-
values << 1
|
38
|
-
end
|
39
|
-
|
40
|
-
e.on [:account, :not_present] do
|
41
|
-
values << 2
|
42
|
-
end
|
43
|
-
|
44
|
-
e.on :terrible_error do
|
45
|
-
values << 3
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
assert [1, 2, 3] == values
|
50
|
-
end
|
51
|
-
|
52
|
-
test "accept case-like matches for an error" do
|
53
|
-
values = []
|
54
|
-
|
55
|
-
@model.errors.present do |e|
|
56
|
-
e.on Array do
|
57
|
-
values << 1
|
58
|
-
end
|
59
|
-
|
60
|
-
e.on :terrible_error do
|
61
|
-
values << 3
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
assert [1, 3] == values
|
66
|
-
end
|
67
|
-
|
68
|
-
test "accept multiple matches for an error" do
|
69
|
-
values = @model.errors.present do |e|
|
70
|
-
e.on [:name, :not_present], "A"
|
71
|
-
e.on [:account, :not_present] do
|
72
|
-
"B"
|
73
|
-
end
|
74
|
-
e.on :terrible_error, "C"
|
75
|
-
end
|
76
|
-
|
77
|
-
assert %w{A B C} == values
|
78
|
-
end
|
79
|
-
|
80
|
-
class MyPresenter < Ohm::Validations::Presenter
|
81
|
-
def on(*args)
|
82
|
-
super(*args) do
|
83
|
-
yield.downcase
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
test "take a custom presenter" do
|
89
|
-
values = @model.errors.present(MyPresenter) do |e|
|
90
|
-
e.on([:name, :not_present]) { "A" }
|
91
|
-
e.on([:account, :not_present]) { "B" }
|
92
|
-
e.on(:terrible_error) { "C" }
|
93
|
-
end
|
94
|
-
|
95
|
-
assert %w{a b c} == values
|
96
|
-
end
|
97
|
-
|
98
|
-
test "raise an error if neither a message nor a block are supplied" do
|
99
|
-
begin
|
100
|
-
Ohm::Validations::Presenter.new([:custom]).present do |e|
|
101
|
-
e.on(:custom)
|
102
|
-
end
|
103
|
-
rescue => e
|
104
|
-
assert e.class == ArgumentError
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
test "not raise an error if the message passed is nil" do
|
109
|
-
values = Ohm::Validations::Presenter.new([:custom]).present do |e|
|
110
|
-
e.on(:custom, nil)
|
111
|
-
end
|
112
|
-
|
113
|
-
assert [nil] == values
|
114
|
-
|
115
|
-
Ohm::Validations::Presenter.new([:custom]).present do |e|
|
116
|
-
e.on(:custom, nil) do
|
117
|
-
raise "Should not call block"
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
data/test/indices_test.rb
DELETED
@@ -1,213 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
-
|
5
|
-
class User < Ohm::Model
|
6
|
-
attribute :email
|
7
|
-
attribute :update
|
8
|
-
attribute :activation_code
|
9
|
-
attribute :sandunga
|
10
|
-
|
11
|
-
index :email
|
12
|
-
index :email_provider
|
13
|
-
index :working_days
|
14
|
-
index :update
|
15
|
-
index :activation_code
|
16
|
-
|
17
|
-
def email_provider
|
18
|
-
email.split("@").last
|
19
|
-
end
|
20
|
-
|
21
|
-
def working_days
|
22
|
-
@working_days ||= []
|
23
|
-
end
|
24
|
-
|
25
|
-
def write
|
26
|
-
self.activation_code ||= "user:#{id}"
|
27
|
-
super
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
setup do
|
32
|
-
@user1 = User.create(:email => "foo", :activation_code => "bar", :update => "baz")
|
33
|
-
@user2 = User.create(:email => "bar")
|
34
|
-
@user3 = User.create(:email => "baz qux")
|
35
|
-
end
|
36
|
-
|
37
|
-
test "be able to find by the given attribute" do
|
38
|
-
assert @user1 == User.find(:email => "foo").first
|
39
|
-
end
|
40
|
-
|
41
|
-
test "raise an error if the parameter supplied is not a hash" do
|
42
|
-
begin
|
43
|
-
User.find(1)
|
44
|
-
rescue => ex
|
45
|
-
ensure
|
46
|
-
assert ex.kind_of?(ArgumentError)
|
47
|
-
assert ex.message == "You need to supply a hash with filters. If you want to find by ID, use User[id] instead."
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
test "avoid intersections with the all collection" do
|
52
|
-
assert "User:email:#{Ohm::Model.encode "foo"}" == User.find(:email => "foo").key.to_s
|
53
|
-
|
54
|
-
assert "~:User:email:Zm9v+User:activation_code:" ==
|
55
|
-
User.find(:email => "foo").find(:activation_code => "").key.to_s
|
56
|
-
|
57
|
-
assert "~:User:email:Zm9v+User:activation_code:YmFy+User:update:YmF6" ==
|
58
|
-
result = User.find(:email => "foo").find(:activation_code => "bar").find(:update => "baz").key.to_s
|
59
|
-
end
|
60
|
-
|
61
|
-
test "use a special namespace for set operations" do
|
62
|
-
assert User.find(:email => "foo", :activation_code => "bar").key.to_s.match(/^~:/)
|
63
|
-
|
64
|
-
assert Ohm.redis.keys("~:*").size > 0
|
65
|
-
end
|
66
|
-
|
67
|
-
test "allow multiple chained finds" do
|
68
|
-
assert 1 == User.find(:email => "foo").find(:activation_code => "bar").find(:update => "baz").size
|
69
|
-
end
|
70
|
-
|
71
|
-
test "raise if the field is not indexed" do
|
72
|
-
assert_raise(Ohm::Model::IndexNotFound) do
|
73
|
-
User.find(:sandunga => "foo")
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
test "return nil if no results are found" do
|
78
|
-
assert User.find(:email => "foobar").empty?
|
79
|
-
assert nil == User.find(:email => "foobar").first
|
80
|
-
end
|
81
|
-
|
82
|
-
test "update indices when changing attribute values" do
|
83
|
-
@user1.email = "baz"
|
84
|
-
@user1.save
|
85
|
-
|
86
|
-
assert [] == User.find(:email => "foo").all
|
87
|
-
assert [@user1] == User.find(:email => "baz").all
|
88
|
-
end
|
89
|
-
|
90
|
-
test "remove from the index after deleting" do
|
91
|
-
@user2.delete
|
92
|
-
|
93
|
-
assert [] == User.find(:email => "bar").all
|
94
|
-
end
|
95
|
-
|
96
|
-
test "work with attributes that contain spaces" do
|
97
|
-
assert [@user3] == User.find(:email => "baz qux").all
|
98
|
-
end
|
99
|
-
|
100
|
-
# Indexing arbitrary attributes
|
101
|
-
setup do
|
102
|
-
@user1 = User.create(:email => "foo@gmail.com")
|
103
|
-
@user2 = User.create(:email => "bar@gmail.com")
|
104
|
-
@user3 = User.create(:email => "bazqux@yahoo.com")
|
105
|
-
end
|
106
|
-
|
107
|
-
test "allow indexing by an arbitrary attribute" do
|
108
|
-
assert [@user1, @user2] == User.find(:email_provider => "gmail.com").to_a.sort_by { |u| u.id }
|
109
|
-
assert [@user3] == User.find(:email_provider => "yahoo.com").all
|
110
|
-
end
|
111
|
-
|
112
|
-
test "allow indexing by an attribute that is lazily set" do
|
113
|
-
assert [@user1] == User.find(:activation_code => "user:1").to_a
|
114
|
-
end
|
115
|
-
|
116
|
-
# Indexing enumerables
|
117
|
-
setup do
|
118
|
-
@user1 = User.create(:email => "foo@gmail.com")
|
119
|
-
@user2 = User.create(:email => "bar@gmail.com")
|
120
|
-
|
121
|
-
@user1.working_days << "Mon"
|
122
|
-
@user1.working_days << "Tue"
|
123
|
-
@user2.working_days << "Mon"
|
124
|
-
@user2.working_days << "Wed"
|
125
|
-
|
126
|
-
@user1.save
|
127
|
-
@user2.save
|
128
|
-
end
|
129
|
-
|
130
|
-
test "index each item" do
|
131
|
-
assert [@user1, @user2] == User.find(:working_days => "Mon").to_a.sort_by { |u| u.id }
|
132
|
-
end
|
133
|
-
|
134
|
-
# TODO If we deal with Ohm collections, the updates are atomic but the reindexing never happens.
|
135
|
-
# One solution may be to reindex after inserts or deletes in collection.
|
136
|
-
test "remove the indices when the object changes" do
|
137
|
-
@user2.working_days.delete "Mon"
|
138
|
-
@user2.save
|
139
|
-
assert [@user1] == User.find(:working_days => "Mon").all
|
140
|
-
end
|
141
|
-
|
142
|
-
# Intersection and difference
|
143
|
-
class Event < Ohm::Model
|
144
|
-
attr_writer :days
|
145
|
-
|
146
|
-
attribute :timeline
|
147
|
-
index :timeline
|
148
|
-
index :days
|
149
|
-
|
150
|
-
def days
|
151
|
-
@days ||= []
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
setup do
|
156
|
-
@event1 = Event.create(:timeline => 1).update(:days => [1, 2])
|
157
|
-
@event2 = Event.create(:timeline => 1).update(:days => [2, 3])
|
158
|
-
@event3 = Event.create(:timeline => 2).update(:days => [3, 4])
|
159
|
-
@event4 = Event.create(:timeline => 2).update(:days => [1, 3])
|
160
|
-
end
|
161
|
-
|
162
|
-
test "intersect multiple sets of results" do
|
163
|
-
assert [@event1] == Event.find(:days => [1, 2]).all
|
164
|
-
assert [@event1] == Event.find(:timeline => 1, :days => [1, 2]).all
|
165
|
-
assert [@event1] == Event.find(:timeline => 1).find(:days => [1, 2]).all
|
166
|
-
end
|
167
|
-
|
168
|
-
test "compute the difference between sets" do
|
169
|
-
assert [@event2] == Event.find(:timeline => 1).except(:days => 1).all
|
170
|
-
end
|
171
|
-
|
172
|
-
test "raise if the argument is not an index" do
|
173
|
-
assert_raise(Ohm::Model::IndexNotFound) do
|
174
|
-
Event.find(:timeline => 1).except(:not_an_index => 1)
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
test "work with strings that generate a new line when encoded" do
|
179
|
-
user = User.create(:email => "foo@bar", :update => "CORRECTED - UPDATE 2-Suspected US missile strike kills 5 in Pakistan")
|
180
|
-
assert [user] == User.find(:update => "CORRECTED - UPDATE 2-Suspected US missile strike kills 5 in Pakistan").all
|
181
|
-
end
|
182
|
-
|
183
|
-
# New indices
|
184
|
-
test "populate a new index when the model is saved" do
|
185
|
-
class Event < Ohm::Model
|
186
|
-
attribute :name
|
187
|
-
end
|
188
|
-
|
189
|
-
foo = Event.create(:name => "Foo")
|
190
|
-
|
191
|
-
assert_raise(Ohm::Model::IndexNotFound) { Event.find(:name => "Foo") }
|
192
|
-
|
193
|
-
class Event < Ohm::Model
|
194
|
-
index :name
|
195
|
-
end
|
196
|
-
|
197
|
-
# Find works correctly once the index is added.
|
198
|
-
Event.find(:name => "Foo")
|
199
|
-
|
200
|
-
# The index was added after foo was created.
|
201
|
-
assert Event.find(:name => "Foo").empty?
|
202
|
-
|
203
|
-
bar = Event.create(:name => "Bar")
|
204
|
-
|
205
|
-
# Bar was indexed properly.
|
206
|
-
assert bar == Event.find(:name => "Bar").first
|
207
|
-
|
208
|
-
# Saving all the objects populates the indices.
|
209
|
-
Event.all.each { |e| e.save }
|
210
|
-
|
211
|
-
# Now foo is indexed.
|
212
|
-
assert foo == Event.find(:name => "Foo").first
|
213
|
-
end
|
data/test/mutex_test.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require File.expand_path("./helper", File.dirname(__FILE__))
|
4
|
-
|
5
|
-
class Person < Ohm::Model
|
6
|
-
attribute :name
|
7
|
-
end
|
8
|
-
|
9
|
-
setup do
|
10
|
-
@p1 = Person.create :name => "Albert"
|
11
|
-
@p2 = Person[1]
|
12
|
-
end
|
13
|
-
|
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
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
p2 = Thread.new do
|
24
|
-
sleep 0.01
|
25
|
-
@p2.mutex do
|
26
|
-
t2 = Time.now
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
p1.join
|
31
|
-
p2.join
|
32
|
-
assert t2 > t1
|
33
|
-
end
|
34
|
-
|
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
|
41
|
-
|
42
|
-
test "work if two clients are fighting for the lock" do
|
43
|
-
@p1.send(:lock!)
|
44
|
-
@p3 = Person[1]
|
45
|
-
@p4 = Person[1]
|
46
|
-
|
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
|
56
|
-
|
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!)
|
65
|
-
|
66
|
-
threads = []
|
67
|
-
|
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)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
threads.each { |t| t.join }
|
83
|
-
assert n * m == @candidate.votes
|
84
|
-
end
|