sohm 0.0.1
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.
- checksums.yaml +7 -0
- data/.gems +4 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +312 -0
- data/LICENSE +19 -0
- data/README.md +10 -0
- data/benchmarks/common.rb +33 -0
- data/benchmarks/create.rb +21 -0
- data/benchmarks/delete.rb +13 -0
- data/examples/activity-feed.rb +162 -0
- data/examples/chaining.rb +162 -0
- data/examples/json-hash.rb +75 -0
- data/examples/one-to-many.rb +124 -0
- data/examples/philosophy.rb +137 -0
- data/examples/redis-logging.txt +179 -0
- data/examples/slug.rb +149 -0
- data/examples/tagging.rb +237 -0
- data/lib/sample.rb +14 -0
- data/lib/sohm/command.rb +51 -0
- data/lib/sohm/json.rb +17 -0
- data/lib/sohm/lua/delete.lua +72 -0
- data/lib/sohm/lua/save.lua +13 -0
- data/lib/sohm.rb +1576 -0
- data/makefile +4 -0
- data/sohm.gemspec +18 -0
- data/test/association.rb +33 -0
- data/test/command.rb +55 -0
- data/test/connection.rb +16 -0
- data/test/core.rb +24 -0
- data/test/counters.rb +67 -0
- data/test/enumerable.rb +79 -0
- data/test/filtering.rb +185 -0
- data/test/hash_key.rb +31 -0
- data/test/helper.rb +23 -0
- data/test/indices.rb +133 -0
- data/test/json.rb +62 -0
- data/test/list.rb +83 -0
- data/test/model.rb +789 -0
- data/test/set.rb +37 -0
- data/test/thread_safety.rb +67 -0
- data/test/to_hash.rb +29 -0
- data/test/uniques.rb +98 -0
- metadata +142 -0
data/makefile
ADDED
data/sohm.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "sohm"
|
3
|
+
s.version = "0.0.1"
|
4
|
+
s.summary = %{Slim ohm for twemproxy-like system}
|
5
|
+
s.description = %Q{Slim ohm is a forked ohm that works with twemproxy-like redis system, only a limited set of features in ohm is supported}
|
6
|
+
s.authors = ["Xuejie Xiao"]
|
7
|
+
s.email = ["xxuejie@gmail.com"]
|
8
|
+
s.homepage = "https://github.com/xxuejie/sohm"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
|
13
|
+
s.add_dependency "redic", "~> 1.4.1"
|
14
|
+
s.add_dependency "nido", "~> 0.0.1"
|
15
|
+
s.add_dependency "msgpack", "~> 0.5.11"
|
16
|
+
|
17
|
+
s.add_development_dependency "cutest", "~> 1.2.2"
|
18
|
+
end
|
data/test/association.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class User < Ohm::Model
|
4
|
+
collection :posts, :Post
|
5
|
+
end
|
6
|
+
|
7
|
+
class Post < Ohm::Model
|
8
|
+
reference :user, :User
|
9
|
+
end
|
10
|
+
|
11
|
+
setup do
|
12
|
+
u = User.create
|
13
|
+
p = Post.create(:user => u)
|
14
|
+
|
15
|
+
[u, p]
|
16
|
+
end
|
17
|
+
|
18
|
+
test "basic shake and bake" do |u, p|
|
19
|
+
assert u.posts.include?(p)
|
20
|
+
|
21
|
+
p = Post[p.id]
|
22
|
+
assert_equal u, p.user
|
23
|
+
end
|
24
|
+
|
25
|
+
test "memoization" do |u, p|
|
26
|
+
# This will read the user instance once.
|
27
|
+
p.user
|
28
|
+
assert_equal p.user, p.instance_variable_get(:@_memo)[:user]
|
29
|
+
|
30
|
+
# This will un-memoize the user instance
|
31
|
+
p.user = u
|
32
|
+
assert_equal nil, p.instance_variable_get(:@_memo)[:user]
|
33
|
+
end
|
data/test/command.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
scope do
|
4
|
+
setup do
|
5
|
+
redis = Redic.new
|
6
|
+
redis.call("FLUSHDB")
|
7
|
+
|
8
|
+
nido = Nido.new("User:tmp")
|
9
|
+
|
10
|
+
[1, 2, 3].each { |i| redis.call("SADD", "A", i) }
|
11
|
+
[1, 4, 5].each { |i| redis.call("SADD", "B", i) }
|
12
|
+
|
13
|
+
[10, 11, 12].each { |i| redis.call("SADD", "C", i) }
|
14
|
+
[11, 12, 13].each { |i| redis.call("SADD", "D", i) }
|
15
|
+
[12, 13, 14].each { |i| redis.call("SADD", "E", i) }
|
16
|
+
|
17
|
+
[10, 11, 12].each { |i| redis.call("SADD", "F", i) }
|
18
|
+
[11, 12, 13].each { |i| redis.call("SADD", "G", i) }
|
19
|
+
[12, 13, 14].each { |i| redis.call("SADD", "H", i) }
|
20
|
+
|
21
|
+
[redis, nido]
|
22
|
+
end
|
23
|
+
|
24
|
+
test "special condition: single argument returns that arg" do
|
25
|
+
assert_equal "A", Ohm::Command[:sinterstore, "A"]
|
26
|
+
end
|
27
|
+
|
28
|
+
test "full stack test" do |redis, nido|
|
29
|
+
cmd1 = Ohm::Command[:sinterstore, "A", "B"]
|
30
|
+
|
31
|
+
res = cmd1.call(nido, redis)
|
32
|
+
assert_equal ["1"], redis.call("SMEMBERS", res)
|
33
|
+
|
34
|
+
cmd1.clean
|
35
|
+
assert_equal 0, redis.call("EXISTS", res)
|
36
|
+
|
37
|
+
cmd2 = Ohm::Command[:sinterstore, "C", "D", "E"]
|
38
|
+
cmd3 = Ohm::Command[:sunionstore, cmd1, cmd2]
|
39
|
+
|
40
|
+
res = cmd3.call(nido, redis)
|
41
|
+
assert_equal ["1", "12"], redis.call("SMEMBERS", res)
|
42
|
+
|
43
|
+
cmd3.clean
|
44
|
+
assert redis.call("KEYS", nido["*"]).empty?
|
45
|
+
|
46
|
+
cmd4 = Ohm::Command[:sinterstore, "F", "G", "H"]
|
47
|
+
cmd5 = Ohm::Command[:sdiffstore, cmd3, cmd4]
|
48
|
+
|
49
|
+
res = cmd5.call(nido, redis)
|
50
|
+
assert_equal ["1"], redis.call("SMEMBERS", res)
|
51
|
+
|
52
|
+
cmd5.clean
|
53
|
+
assert redis.call("KEYS", nido["*"]).empty?
|
54
|
+
end
|
55
|
+
end
|
data/test/connection.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
test "model inherits Ohm.redis connection by default" do
|
4
|
+
class C < Ohm::Model
|
5
|
+
end
|
6
|
+
|
7
|
+
assert_equal C.redis.url, Ohm.redis.url
|
8
|
+
end
|
9
|
+
|
10
|
+
test "model can define its own connection" do
|
11
|
+
class B < Ohm::Model
|
12
|
+
self.redis = Redic.new("redis://localhost:6379/1")
|
13
|
+
end
|
14
|
+
|
15
|
+
assert B.redis.url != Ohm.redis.url
|
16
|
+
end
|
data/test/core.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class Event < Ohm::Model
|
4
|
+
attribute :name
|
5
|
+
attribute :location
|
6
|
+
end
|
7
|
+
|
8
|
+
test "assign attributes from the hash" do
|
9
|
+
event = Event.new(name: "Ruby Tuesday")
|
10
|
+
assert_equal event.name, "Ruby Tuesday"
|
11
|
+
end
|
12
|
+
|
13
|
+
test "assign an ID and save the object" do
|
14
|
+
event1 = Event.create(name: "Ruby Tuesday")
|
15
|
+
event2 = Event.create(name: "Ruby Meetup")
|
16
|
+
|
17
|
+
assert_equal "1", event1.id
|
18
|
+
assert_equal "2", event2.id
|
19
|
+
end
|
20
|
+
|
21
|
+
test "save the attributes in UTF8" do
|
22
|
+
event = Event.create(name: "32° Kisei-sen")
|
23
|
+
assert_equal "32° Kisei-sen", Event[event.id].name
|
24
|
+
end
|
data/test/counters.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
$VERBOSE = false
|
4
|
+
|
5
|
+
class Ad < Ohm::Model
|
6
|
+
end
|
7
|
+
|
8
|
+
test "counters aren't overwritten by competing saves" do
|
9
|
+
Ad.counter :hits
|
10
|
+
|
11
|
+
instance1 = Ad.create
|
12
|
+
instance1.incr :hits
|
13
|
+
|
14
|
+
instance2 = Ad[instance1.id]
|
15
|
+
|
16
|
+
instance1.incr :hits
|
17
|
+
instance1.incr :hits
|
18
|
+
|
19
|
+
instance2.save
|
20
|
+
|
21
|
+
instance1 = Ad[instance1.id]
|
22
|
+
assert_equal 3, instance1.hits
|
23
|
+
end
|
24
|
+
|
25
|
+
test "you can increment counters even when attributes is empty" do
|
26
|
+
Ad.counter :hits
|
27
|
+
|
28
|
+
ad = Ad.create
|
29
|
+
ad = Ad[ad.id]
|
30
|
+
|
31
|
+
ex = nil
|
32
|
+
|
33
|
+
begin
|
34
|
+
ad.incr :hits
|
35
|
+
rescue ArgumentError => e
|
36
|
+
ex = e
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_equal nil, ex
|
40
|
+
end
|
41
|
+
|
42
|
+
test "an attribute gets saved properly" do
|
43
|
+
Ad.attribute :name
|
44
|
+
Ad.counter :hits
|
45
|
+
|
46
|
+
ad = Ad.create(:name => "foo")
|
47
|
+
ad.incr :hits, 10
|
48
|
+
assert_equal 10, ad.hits
|
49
|
+
|
50
|
+
# Now let's just load and save it.
|
51
|
+
ad = Ad[ad.id]
|
52
|
+
ad.save
|
53
|
+
|
54
|
+
# The attributes should remain the same
|
55
|
+
ad = Ad[ad.id]
|
56
|
+
assert_equal "foo", ad.name
|
57
|
+
assert_equal 10, ad.hits
|
58
|
+
|
59
|
+
# If we load and save again while we incr behind the scenes,
|
60
|
+
# the latest counter values should be respected.
|
61
|
+
ad = Ad[ad.id]
|
62
|
+
ad.incr :hits, 5
|
63
|
+
ad.save
|
64
|
+
|
65
|
+
ad = Ad[ad.id]
|
66
|
+
assert_equal 15, ad.hits
|
67
|
+
end
|
data/test/enumerable.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
scope do
|
4
|
+
class Contact < Ohm::Model
|
5
|
+
attribute :name
|
6
|
+
end
|
7
|
+
|
8
|
+
setup do
|
9
|
+
john = Contact.create(name: "John Doe")
|
10
|
+
jane = Contact.create(name: "Jane Doe")
|
11
|
+
|
12
|
+
[john, jane]
|
13
|
+
end
|
14
|
+
|
15
|
+
test "Set#size doesn't do each" do
|
16
|
+
set = Contact.all
|
17
|
+
|
18
|
+
def set.each
|
19
|
+
raise "Failed"
|
20
|
+
end
|
21
|
+
|
22
|
+
assert_equal 2, set.size
|
23
|
+
end
|
24
|
+
|
25
|
+
test "Set#each as an Enumerator" do |john, jane|
|
26
|
+
enum = Contact.all.each
|
27
|
+
|
28
|
+
enum.each do |c|
|
29
|
+
assert c == john || c == jane
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
test "select" do |john, jane|
|
34
|
+
assert_equal 2, Contact.all.count
|
35
|
+
assert_equal [john], Contact.all.select { |c| c.id == john.id }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
scope do
|
40
|
+
class Comment < Ohm::Model
|
41
|
+
end
|
42
|
+
|
43
|
+
class Post < Ohm::Model
|
44
|
+
list :comments, :Comment
|
45
|
+
end
|
46
|
+
|
47
|
+
setup do
|
48
|
+
c1 = Comment.create
|
49
|
+
c2 = Comment.create
|
50
|
+
|
51
|
+
post = Post.create
|
52
|
+
post.comments.push(c1)
|
53
|
+
post.comments.push(c2)
|
54
|
+
|
55
|
+
[post, c1, c2]
|
56
|
+
end
|
57
|
+
|
58
|
+
test "List#select" do |post, c1, c2|
|
59
|
+
assert_equal [c1], post.comments.select { |comment| comment == c1 }
|
60
|
+
end
|
61
|
+
|
62
|
+
test "List#each as Enumerator" do |post, c1, c2|
|
63
|
+
enum = post.comments.each
|
64
|
+
|
65
|
+
enum.each do |comment|
|
66
|
+
assert comment == c1 || comment == c2
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
test "List#size doesn't do each" do |post, c1, c2|
|
71
|
+
list = post.comments
|
72
|
+
|
73
|
+
def list.each
|
74
|
+
raise "Failed"
|
75
|
+
end
|
76
|
+
|
77
|
+
assert_equal 2, list.size
|
78
|
+
end
|
79
|
+
end
|
data/test/filtering.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class User < Ohm::Model
|
4
|
+
attribute :fname
|
5
|
+
attribute :lname
|
6
|
+
attribute :status
|
7
|
+
|
8
|
+
index :fname
|
9
|
+
index :lname
|
10
|
+
index :status
|
11
|
+
end
|
12
|
+
|
13
|
+
setup do
|
14
|
+
u1 = User.create(:fname => "John", :lname => "Doe", :status => "active")
|
15
|
+
u2 = User.create(:fname => "Jane", :lname => "Doe", :status => "active")
|
16
|
+
|
17
|
+
[u1, u2]
|
18
|
+
end
|
19
|
+
|
20
|
+
test "findability" do |john, jane|
|
21
|
+
assert_equal 1, User.find(:lname => "Doe", :fname => "John").size
|
22
|
+
assert User.find(:lname => "Doe", :fname => "John").include?(john)
|
23
|
+
|
24
|
+
assert_equal 1, User.find(:lname => "Doe", :fname => "Jane").size
|
25
|
+
assert User.find(:lname => "Doe", :fname => "Jane").include?(jane)
|
26
|
+
end
|
27
|
+
|
28
|
+
test "sets aren't mutable" do |john, jane|
|
29
|
+
assert_raise NoMethodError do
|
30
|
+
User.find(:lname => "Doe").add(john)
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_raise NoMethodError do
|
34
|
+
User.find(:lname => "Doe", :fname => "John").add(john)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
test "#first" do |john, jane|
|
39
|
+
set = User.find(:lname => "Doe", :status => "active")
|
40
|
+
|
41
|
+
assert_equal jane, set.first(:by => "fname", :order => "ALPHA")
|
42
|
+
assert_equal john, set.first(:by => "fname", :order => "ALPHA DESC")
|
43
|
+
|
44
|
+
assert_equal "Jane", set.first(:by => "fname", :order => "ALPHA", :get => "fname")
|
45
|
+
assert_equal "John", set.first(:by => "fname", :order => "ALPHA DESC", :get => "fname")
|
46
|
+
end
|
47
|
+
|
48
|
+
test "#[]" do |john, jane|
|
49
|
+
set = User.find(:lname => "Doe", :status => "active")
|
50
|
+
|
51
|
+
assert_equal john, set[john.id]
|
52
|
+
assert_equal jane, set[jane.id]
|
53
|
+
end
|
54
|
+
|
55
|
+
test "#except" do |john, jane|
|
56
|
+
User.create(:status => "inactive", :lname => "Doe")
|
57
|
+
|
58
|
+
res = User.find(:lname => "Doe").except(:status => "inactive")
|
59
|
+
|
60
|
+
assert_equal 2, res.size
|
61
|
+
assert res.include?(john)
|
62
|
+
assert res.include?(jane)
|
63
|
+
|
64
|
+
res = User.all.except(:status => "inactive")
|
65
|
+
|
66
|
+
assert_equal 2, res.size
|
67
|
+
assert res.include?(john)
|
68
|
+
assert res.include?(jane)
|
69
|
+
end
|
70
|
+
|
71
|
+
test "#except unions keys when passing an array" do |john, jane|
|
72
|
+
expected = User.create(:fname => "Jean", :status => "inactive")
|
73
|
+
|
74
|
+
res = User.find(:status => "inactive").except(:fname => [john.fname, jane.fname])
|
75
|
+
|
76
|
+
assert_equal 1, res.size
|
77
|
+
assert res.include?(expected)
|
78
|
+
|
79
|
+
res = User.all.except(:fname => [john.fname, jane.fname])
|
80
|
+
|
81
|
+
assert_equal 1, res.size
|
82
|
+
assert res.include?(expected)
|
83
|
+
end
|
84
|
+
|
85
|
+
test "indices bug related to a nil attribute" do |john, jane|
|
86
|
+
# First we create a record with a nil attribute
|
87
|
+
out = User.create(:status => nil, :lname => "Doe")
|
88
|
+
|
89
|
+
# Then, we update the old nil attribute to a different
|
90
|
+
# non-nil, value.
|
91
|
+
out.update(status: "inactive")
|
92
|
+
|
93
|
+
# At this point, the index for the nil attribute should
|
94
|
+
# have been cleared.
|
95
|
+
assert_equal 0, User.redis.call("SCARD", "User:indices:status:")
|
96
|
+
end
|
97
|
+
|
98
|
+
test "#union" do |john, jane|
|
99
|
+
User.create(:status => "super", :lname => "Doe")
|
100
|
+
included = User.create(:status => "inactive", :lname => "Doe")
|
101
|
+
|
102
|
+
res = User.find(:status => "active").union(:status => "inactive")
|
103
|
+
|
104
|
+
assert_equal 3, res.size
|
105
|
+
assert res.include?(john)
|
106
|
+
assert res.include?(jane)
|
107
|
+
assert res.include?(included)
|
108
|
+
|
109
|
+
res = User.find(:status => "active").union(:status => "inactive").find(:lname => "Doe")
|
110
|
+
|
111
|
+
assert res.any? { |e| e.status == "inactive" }
|
112
|
+
end
|
113
|
+
|
114
|
+
test "#combine" do |john, jane|
|
115
|
+
res = User.find(:status => "active").combine(fname: ["John", "Jane"])
|
116
|
+
|
117
|
+
assert_equal 2, res.size
|
118
|
+
assert res.include?(john)
|
119
|
+
assert res.include?(jane)
|
120
|
+
end
|
121
|
+
|
122
|
+
# book author thing via @myobie
|
123
|
+
scope do
|
124
|
+
class Book < Ohm::Model
|
125
|
+
collection :authors, :Author
|
126
|
+
end
|
127
|
+
|
128
|
+
class Author < Ohm::Model
|
129
|
+
reference :book, :Book
|
130
|
+
|
131
|
+
attribute :mood
|
132
|
+
index :mood
|
133
|
+
end
|
134
|
+
|
135
|
+
setup do
|
136
|
+
book1 = Book.create
|
137
|
+
book2 = Book.create
|
138
|
+
|
139
|
+
Author.create(:book => book1, :mood => "happy")
|
140
|
+
Author.create(:book => book1, :mood => "sad")
|
141
|
+
Author.create(:book => book2, :mood => "sad")
|
142
|
+
|
143
|
+
[book1, book2]
|
144
|
+
end
|
145
|
+
|
146
|
+
test "straight up intersection + union" do |book1, book2|
|
147
|
+
result = book1.authors.find(:mood => "happy").
|
148
|
+
union(:book_id => book1.id, :mood => "sad")
|
149
|
+
|
150
|
+
assert_equal 2, result.size
|
151
|
+
end
|
152
|
+
|
153
|
+
test "appending an empty set via union" do |book1, book2|
|
154
|
+
res = Author.find(:book_id => book1.id, :mood => "happy").
|
155
|
+
union(:book_id => book2.id, :mood => "sad").
|
156
|
+
union(:book_id => book2.id, :mood => "happy")
|
157
|
+
|
158
|
+
assert_equal 2, res.size
|
159
|
+
end
|
160
|
+
|
161
|
+
test "revert by applying the original intersection" do |book1, book2|
|
162
|
+
res = Author.find(:book_id => book1.id, :mood => "happy").
|
163
|
+
union(:book_id => book2.id, :mood => "sad").
|
164
|
+
find(:book_id => book1.id, :mood => "happy")
|
165
|
+
|
166
|
+
assert_equal 1, res.size
|
167
|
+
end
|
168
|
+
|
169
|
+
test "remove original intersection by doing diff" do |book1, book2|
|
170
|
+
res = Author.find(:book_id => book1.id, :mood => "happy").
|
171
|
+
union(:book_id => book2.id, :mood => "sad").
|
172
|
+
except(:book_id => book1.id, :mood => "happy")
|
173
|
+
|
174
|
+
assert_equal 1, res.size
|
175
|
+
assert res.map(&:mood).include?("sad")
|
176
|
+
assert res.map(&:book_id).include?(book2.id)
|
177
|
+
end
|
178
|
+
|
179
|
+
test "@myobie usecase" do |book1, book2|
|
180
|
+
res = book1.authors.find(:mood => "happy").
|
181
|
+
union(:mood => "sad", :book_id => book1.id)
|
182
|
+
|
183
|
+
assert_equal 2, res.size
|
184
|
+
end
|
185
|
+
end
|
data/test/hash_key.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class Tag < Ohm::Model
|
4
|
+
attribute :name
|
5
|
+
end
|
6
|
+
|
7
|
+
test "using a new record as a hash key" do
|
8
|
+
tag = Tag.new
|
9
|
+
hash = { tag => "Ruby" }
|
10
|
+
|
11
|
+
assert "Ruby" == hash[tag]
|
12
|
+
assert hash[Tag.new].nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
test "on a persisted model" do
|
16
|
+
tag = Tag.create(:name => "Ruby")
|
17
|
+
|
18
|
+
assert "Ruby" == { tag => "Ruby" }[tag]
|
19
|
+
end
|
20
|
+
|
21
|
+
test "on a reloaded model" do
|
22
|
+
tag = Tag.create(:name => "Ruby")
|
23
|
+
hash = { tag => "Ruby" }
|
24
|
+
|
25
|
+
tag = Tag[tag.id]
|
26
|
+
assert "Ruby" == hash[tag]
|
27
|
+
end
|
28
|
+
|
29
|
+
test "on attributes class method" do
|
30
|
+
assert [:name] == Tag.attributes
|
31
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
begin
|
2
|
+
require "ruby-debug"
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
6
|
+
require "cutest"
|
7
|
+
|
8
|
+
def silence_warnings
|
9
|
+
original_verbose, $VERBOSE = $VERBOSE, nil
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
$VERBOSE = original_verbose
|
13
|
+
end unless defined?(silence_warnings)
|
14
|
+
|
15
|
+
$VERBOSE = true
|
16
|
+
|
17
|
+
require_relative "../lib/ohm"
|
18
|
+
|
19
|
+
Ohm.redis = Redic.new("redis://127.0.0.1:6379")
|
20
|
+
|
21
|
+
prepare do
|
22
|
+
Ohm.redis.call("FLUSHALL")
|
23
|
+
end
|
data/test/indices.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class User < Ohm::Model
|
4
|
+
attribute :email
|
5
|
+
attribute :update
|
6
|
+
attribute :activation_code
|
7
|
+
attribute :sandunga
|
8
|
+
index :email
|
9
|
+
index :email_provider
|
10
|
+
index :working_days
|
11
|
+
index :update
|
12
|
+
index :activation_code
|
13
|
+
|
14
|
+
def working_days
|
15
|
+
@working_days ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def email_provider
|
19
|
+
email.split("@").last
|
20
|
+
end
|
21
|
+
|
22
|
+
def before_save
|
23
|
+
self.activation_code ||= "user:#{id}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
setup do
|
28
|
+
@user1 = User.create(:email => "foo", :activation_code => "bar", :update => "baz")
|
29
|
+
@user2 = User.create(:email => "bar")
|
30
|
+
@user3 = User.create(:email => "baz qux")
|
31
|
+
end
|
32
|
+
|
33
|
+
test "be able to find by the given attribute" do
|
34
|
+
assert @user1 == User.find(:email => "foo").first
|
35
|
+
end
|
36
|
+
|
37
|
+
test "raise if the index doesn't exist" do
|
38
|
+
assert_raise Ohm::IndexNotFound do
|
39
|
+
User.find(:address => "foo")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
test "raise an error if the parameter supplied is not a hash" do
|
44
|
+
begin
|
45
|
+
User.find(1)
|
46
|
+
rescue => ex
|
47
|
+
ensure
|
48
|
+
assert ex.kind_of?(ArgumentError)
|
49
|
+
assert ex.message == "You need to supply a hash with filters. If you want to find by ID, use User[id] instead."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
test "avoid intersections with the all collection" do
|
54
|
+
assert_equal "User:indices:email:foo", User.find(:email => "foo").key
|
55
|
+
end
|
56
|
+
|
57
|
+
test "cleanup the temporary key after use" do
|
58
|
+
assert User.find(:email => "foo", :activation_code => "bar").to_a
|
59
|
+
|
60
|
+
assert Ohm.redis.call("KEYS", "User:temp:*").empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
test "allow multiple chained finds" do
|
64
|
+
assert 1 == User.find(:email => "foo").find(:activation_code => "bar").find(:update => "baz").size
|
65
|
+
end
|
66
|
+
|
67
|
+
test "return nil if no results are found" do
|
68
|
+
assert User.find(:email => "foobar").empty?
|
69
|
+
assert nil == User.find(:email => "foobar").first
|
70
|
+
end
|
71
|
+
|
72
|
+
test "update indices when changing attribute values" do
|
73
|
+
@user1.email = "baz"
|
74
|
+
@user1.save
|
75
|
+
|
76
|
+
assert [] == User.find(:email => "foo").to_a
|
77
|
+
assert [@user1] == User.find(:email => "baz").to_a
|
78
|
+
end
|
79
|
+
|
80
|
+
test "remove from the index after deleting" do
|
81
|
+
@user2.delete
|
82
|
+
|
83
|
+
assert [] == User.find(:email => "bar").to_a
|
84
|
+
end
|
85
|
+
|
86
|
+
test "work with attributes that contain spaces" do
|
87
|
+
assert [@user3] == User.find(:email => "baz qux").to_a
|
88
|
+
end
|
89
|
+
|
90
|
+
# Indexing arbitrary attributes
|
91
|
+
setup do
|
92
|
+
@user1 = User.create(:email => "foo@gmail.com")
|
93
|
+
@user2 = User.create(:email => "bar@gmail.com")
|
94
|
+
@user3 = User.create(:email => "bazqux@yahoo.com")
|
95
|
+
end
|
96
|
+
|
97
|
+
test "allow indexing by an arbitrary attribute" do
|
98
|
+
gmail = User.find(:email_provider => "gmail.com").to_a
|
99
|
+
assert [@user1, @user2] == gmail.sort_by { |u| u.id }
|
100
|
+
assert [@user3] == User.find(:email_provider => "yahoo.com").to_a
|
101
|
+
end
|
102
|
+
|
103
|
+
scope do
|
104
|
+
# Just to give more context around this bug, basically it happens
|
105
|
+
# when you define a virtual unique or index.
|
106
|
+
#
|
107
|
+
# Previously it was unable to cleanup the indices mainly because
|
108
|
+
# it relied on the attributes being set.
|
109
|
+
class Node < Ohm::Model
|
110
|
+
index :available
|
111
|
+
attribute :capacity
|
112
|
+
|
113
|
+
unique :available
|
114
|
+
|
115
|
+
def available
|
116
|
+
capacity.to_i <= 90
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
test "index bug" do
|
121
|
+
n = Node.create
|
122
|
+
n.update(capacity: 91)
|
123
|
+
|
124
|
+
assert_equal 0, Node.find(available: true).size
|
125
|
+
end
|
126
|
+
|
127
|
+
test "uniques bug" do
|
128
|
+
n = Node.create
|
129
|
+
n.update(capacity: 91)
|
130
|
+
|
131
|
+
assert_equal nil, Node.with(:available, true)
|
132
|
+
end
|
133
|
+
end
|