ohm_util 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gems +4 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +408 -0
- data/CONTRIBUTING +19 -0
- data/LICENSE +19 -0
- data/README.md +570 -0
- data/benchmarks/common.rb +28 -0
- data/benchmarks/create.rb +21 -0
- data/benchmarks/delete.rb +13 -0
- data/examples/activity-feed.rb +157 -0
- data/examples/chaining.rb +162 -0
- data/examples/json-hash.rb +75 -0
- data/examples/one-to-many.rb +118 -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/lua/delete.lua +72 -0
- data/lib/lua/save.lua +126 -0
- data/lib/ohm_util.rb +116 -0
- data/makefile +9 -0
- data/ohm-util.gemspec +14 -0
- data/test/association.rb +33 -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 +138 -0
- data/test/json.rb +62 -0
- data/test/list.rb +83 -0
- data/test/model.rb +819 -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 +108 -0
- metadata +97 -0
data/makefile
ADDED
data/ohm-util.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "ohm_util"
|
3
|
+
s.version = "0.1"
|
4
|
+
s.summary = %{Object-hash mapping library for Redis.}
|
5
|
+
s.description = %Q{Ohm is a library that allows to store an object in Redis, a persistent key-value database. It has very good performance.}
|
6
|
+
s.authors = ["Travis Liu"]
|
7
|
+
s.email = ["travisliu.tw@gmail.com"]
|
8
|
+
s.homepage = "https://github.com/travisliu/ohm-util"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
|
13
|
+
s.add_dependency "redic", "~> 1.5.0"
|
14
|
+
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/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.increment :hits
|
13
|
+
|
14
|
+
instance2 = Ad[instance1.id]
|
15
|
+
|
16
|
+
instance1.increment :hits
|
17
|
+
instance1.increment :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.increment :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.increment :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.increment :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
|