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.
data/makefile ADDED
@@ -0,0 +1,4 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ cutest -r ./test/helper.rb ./test/*.rb
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
@@ -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
@@ -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
@@ -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