ohm_util 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 +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/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,138 @@
|
|
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.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
test "allow multiple chained finds" do
|
58
|
+
assert 1 == User.find(:email => "foo").find(:activation_code => "bar").find(:update => "baz").size
|
59
|
+
end
|
60
|
+
|
61
|
+
test "return nil if no results are found" do
|
62
|
+
assert User.find(:email => "foobar").empty?
|
63
|
+
assert nil == User.find(:email => "foobar").first
|
64
|
+
end
|
65
|
+
|
66
|
+
test "update indices when changing attribute values" do
|
67
|
+
@user1.email = "baz"
|
68
|
+
@user1.save
|
69
|
+
|
70
|
+
assert [] == User.find(:email => "foo").to_a
|
71
|
+
assert [@user1] == User.find(:email => "baz").to_a
|
72
|
+
end
|
73
|
+
|
74
|
+
test "remove from the index after deleting" do
|
75
|
+
@user2.delete
|
76
|
+
|
77
|
+
assert [] == User.find(:email => "bar").to_a
|
78
|
+
end
|
79
|
+
|
80
|
+
test "work with attributes that contain spaces" do
|
81
|
+
assert [@user3] == User.find(:email => "baz qux").to_a
|
82
|
+
end
|
83
|
+
|
84
|
+
# Indexing arbitrary attributes
|
85
|
+
setup do
|
86
|
+
@user1 = User.create(:email => "foo@gmail.com")
|
87
|
+
@user2 = User.create(:email => "bar@gmail.com")
|
88
|
+
@user3 = User.create(:email => "bazqux@yahoo.com")
|
89
|
+
end
|
90
|
+
|
91
|
+
test "allow indexing by an arbitrary attribute" do
|
92
|
+
gmail = User.find(:email_provider => "gmail.com").to_a
|
93
|
+
assert [@user1, @user2] == gmail.sort_by { |u| u.id }
|
94
|
+
assert [@user3] == User.find(:email_provider => "yahoo.com").to_a
|
95
|
+
end
|
96
|
+
|
97
|
+
scope do
|
98
|
+
# Just to give more context around this bug, basically it happens
|
99
|
+
# when you define a virtual unique or index.
|
100
|
+
#
|
101
|
+
# Previously it was unable to cleanup the indices mainly because
|
102
|
+
# it relied on the attributes being set.
|
103
|
+
class Node < Ohm::Model
|
104
|
+
index :available
|
105
|
+
attribute :capacity
|
106
|
+
|
107
|
+
unique :available
|
108
|
+
|
109
|
+
def available
|
110
|
+
capacity.to_i <= 90
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
test "index bug" do
|
115
|
+
n = Node.create
|
116
|
+
n.update(capacity: 91)
|
117
|
+
|
118
|
+
assert_equal 0, Node.find(available: true).size
|
119
|
+
end
|
120
|
+
|
121
|
+
test "uniques bug" do
|
122
|
+
n = Node.create
|
123
|
+
n.update(capacity: 91)
|
124
|
+
|
125
|
+
assert_equal nil, Node.with(:available, true)
|
126
|
+
end
|
127
|
+
|
128
|
+
test "float to string" do
|
129
|
+
u1 = User.create(:email => "foo", :update => 3.0)
|
130
|
+
u2 = User.create(:email => "bar", :update => 3)
|
131
|
+
|
132
|
+
assert User.find(:update => 3.0).include?(u1)
|
133
|
+
assert User.find(:update => 3).include?(u2)
|
134
|
+
|
135
|
+
assert !User.find(:update => 3.0).include?(u2)
|
136
|
+
assert !User.find(:update => 3).include?(u1)
|
137
|
+
end
|
138
|
+
end
|
data/test/json.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
require_relative "../lib/ohm/json"
|
3
|
+
|
4
|
+
class Venue < Ohm::Model
|
5
|
+
attribute :name
|
6
|
+
list :programmers, :Programmer
|
7
|
+
end
|
8
|
+
|
9
|
+
class Programmer < Ohm::Model
|
10
|
+
attribute :language
|
11
|
+
|
12
|
+
index :language
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
super.merge(language: language)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
test "exports model.to_hash to json" do
|
20
|
+
assert_equal Hash.new, JSON.parse(Venue.new.to_json)
|
21
|
+
|
22
|
+
venue = Venue.create(name: "foo")
|
23
|
+
json = JSON.parse(venue.to_json)
|
24
|
+
assert_equal venue.id, json["id"]
|
25
|
+
assert_equal nil, json["name"]
|
26
|
+
|
27
|
+
programmer = Programmer.create(language: "Ruby")
|
28
|
+
json = JSON.parse(programmer.to_json)
|
29
|
+
|
30
|
+
assert_equal programmer.id, json["id"]
|
31
|
+
assert_equal programmer.language, json["language"]
|
32
|
+
end
|
33
|
+
|
34
|
+
test "exports a set to json" do
|
35
|
+
Programmer.create(language: "Ruby")
|
36
|
+
Programmer.create(language: "Python")
|
37
|
+
|
38
|
+
expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
|
39
|
+
|
40
|
+
assert_equal expected, Programmer.all.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
test "exports a multiset to json" do
|
44
|
+
Programmer.create(language: "Ruby")
|
45
|
+
Programmer.create(language: "Python")
|
46
|
+
|
47
|
+
expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
|
48
|
+
result = Programmer.find(language: "Ruby").union(language: "Python").to_json
|
49
|
+
|
50
|
+
assert_equal expected, result
|
51
|
+
end
|
52
|
+
|
53
|
+
test "exports a list to json" do
|
54
|
+
venue = Venue.create(name: "Foo")
|
55
|
+
|
56
|
+
venue.programmers.push(Programmer.create(language: "Ruby"))
|
57
|
+
venue.programmers.push(Programmer.create(language: "Python"))
|
58
|
+
|
59
|
+
expected = [{ id: "1", language: "Ruby" }, { id: "2", language: "Python"}].to_json
|
60
|
+
|
61
|
+
assert_equal expected, venue.programmers.to_json
|
62
|
+
end
|
data/test/list.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class Post < Ohm::Model
|
4
|
+
list :comments, :Comment
|
5
|
+
end
|
6
|
+
|
7
|
+
class Comment < Ohm::Model
|
8
|
+
end
|
9
|
+
|
10
|
+
setup do
|
11
|
+
post = Post.create
|
12
|
+
|
13
|
+
post.comments.push(c1 = Comment.create)
|
14
|
+
post.comments.push(c2 = Comment.create)
|
15
|
+
post.comments.push(c3 = Comment.create)
|
16
|
+
|
17
|
+
[post, c1, c2, c3]
|
18
|
+
end
|
19
|
+
|
20
|
+
test "include?" do |p, c1, c2, c3|
|
21
|
+
assert p.comments.include?(c1)
|
22
|
+
assert p.comments.include?(c2)
|
23
|
+
assert p.comments.include?(c3)
|
24
|
+
end
|
25
|
+
|
26
|
+
test "first / last / size / empty?" do |p, c1, c2, c3|
|
27
|
+
assert_equal 3, p.comments.size
|
28
|
+
assert_equal c1, p.comments.first
|
29
|
+
assert_equal c3, p.comments.last
|
30
|
+
assert ! p.comments.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
test "replace" do |p, c1, c2, c3|
|
34
|
+
c4 = Comment.create
|
35
|
+
|
36
|
+
p.comments.replace([c4])
|
37
|
+
|
38
|
+
assert_equal [c4], p.comments.to_a
|
39
|
+
end
|
40
|
+
|
41
|
+
test "push / unshift" do |p, c1, c2, c3|
|
42
|
+
c4 = Comment.create
|
43
|
+
c5 = Comment.create
|
44
|
+
|
45
|
+
p.comments.unshift(c4)
|
46
|
+
p.comments.push(c5)
|
47
|
+
|
48
|
+
assert_equal c4, p.comments.first
|
49
|
+
assert_equal c5, p.comments.last
|
50
|
+
end
|
51
|
+
|
52
|
+
test "delete" do |p, c1, c2, c3|
|
53
|
+
p.comments.delete(c1)
|
54
|
+
assert_equal 2, p.comments.size
|
55
|
+
assert ! p.comments.include?(c1)
|
56
|
+
|
57
|
+
p.comments.delete(c2)
|
58
|
+
assert_equal 1, p.comments.size
|
59
|
+
assert ! p.comments.include?(c2)
|
60
|
+
|
61
|
+
p.comments.delete(c3)
|
62
|
+
assert p.comments.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
test "deleting main model cleans up the collection" do |p, _, _, _|
|
66
|
+
p.delete
|
67
|
+
|
68
|
+
assert_equal 0, Ohm.redis.call("EXISTS", p.key[:comments])
|
69
|
+
end
|
70
|
+
|
71
|
+
test "#ids returns an array with the ids" do |post, *comments|
|
72
|
+
assert_equal comments.map(&:id), post.comments.ids
|
73
|
+
end
|
74
|
+
|
75
|
+
test "range" do |p, c1, c2, c3|
|
76
|
+
assert_equal 3, p.comments.range(0, 100).size
|
77
|
+
assert_equal [c1, c2, c3], p.comments.range(0, 2)
|
78
|
+
assert_equal [c1, c2], p.comments.range(0, 1)
|
79
|
+
assert_equal [c2, c3], p.comments.range(1, 2)
|
80
|
+
assert_equal [c1, c2, c3], p.comments.range(0, -1)
|
81
|
+
assert_equal [c1, c2], p.comments.range(0, -2)
|
82
|
+
assert_equal [c2, c3], p.comments.range(1, -1)
|
83
|
+
end
|
data/test/model.rb
ADDED
@@ -0,0 +1,819 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
require "ostruct"
|
3
|
+
|
4
|
+
class Post < Ohm::Model
|
5
|
+
attribute :body
|
6
|
+
attribute :published
|
7
|
+
set :related, :Post
|
8
|
+
end
|
9
|
+
|
10
|
+
class User < Ohm::Model
|
11
|
+
attribute :email
|
12
|
+
set :posts, :Post
|
13
|
+
end
|
14
|
+
|
15
|
+
class Person < Ohm::Model
|
16
|
+
attribute :name
|
17
|
+
counter :logins
|
18
|
+
index :initial
|
19
|
+
|
20
|
+
def initial
|
21
|
+
name[0, 1].upcase if name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Event < Ohm::Model
|
26
|
+
attribute :name
|
27
|
+
counter :votes
|
28
|
+
set :attendees, :Person
|
29
|
+
|
30
|
+
attribute :slug
|
31
|
+
|
32
|
+
def save
|
33
|
+
self.slug = name.to_s.downcase
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module SomeNamespace
|
39
|
+
class Foo < Ohm::Model
|
40
|
+
attribute :name
|
41
|
+
end
|
42
|
+
|
43
|
+
class Bar < Ohm::Model
|
44
|
+
reference :foo, 'SomeNamespace::Foo'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Meetup < Ohm::Model
|
49
|
+
attribute :name
|
50
|
+
attribute :location
|
51
|
+
end
|
52
|
+
|
53
|
+
test "booleans" do
|
54
|
+
post = Post.new(body: true, published: false)
|
55
|
+
|
56
|
+
post.save
|
57
|
+
|
58
|
+
assert_equal true, post.body
|
59
|
+
assert_equal false, post.published
|
60
|
+
|
61
|
+
post = Post[post.id]
|
62
|
+
|
63
|
+
assert_equal "true", post.body
|
64
|
+
assert_equal nil, post.published
|
65
|
+
end
|
66
|
+
|
67
|
+
test "empty model is ok" do
|
68
|
+
class Foo < Ohm::Model
|
69
|
+
end
|
70
|
+
|
71
|
+
Foo.create
|
72
|
+
end
|
73
|
+
|
74
|
+
test "counters are cleaned up during deletion" do
|
75
|
+
e = Event.create(:name => "Foo")
|
76
|
+
e.increment :votes, 10
|
77
|
+
|
78
|
+
assert_equal 10, e.votes
|
79
|
+
|
80
|
+
e.delete
|
81
|
+
assert_equal 0, Event.redis.call("EXISTS", e.key[:counters])
|
82
|
+
end
|
83
|
+
|
84
|
+
test "get" do
|
85
|
+
m = Meetup.create(:name => "Foo")
|
86
|
+
m.name = "Bar"
|
87
|
+
|
88
|
+
assert_equal "Foo", m.get(:name)
|
89
|
+
assert_equal "Foo", m.name
|
90
|
+
end
|
91
|
+
|
92
|
+
test "set" do
|
93
|
+
m = Meetup.create(:name => "Foo")
|
94
|
+
|
95
|
+
m.set :name, "Bar"
|
96
|
+
assert_equal "Bar", m.name
|
97
|
+
|
98
|
+
m = Meetup[m.id]
|
99
|
+
assert_equal "Bar", m.name
|
100
|
+
|
101
|
+
# Deletes when value is nil.
|
102
|
+
m.set :name, nil
|
103
|
+
m = Meetup[m.id]
|
104
|
+
assert_equal 0, Meetup.redis.call("HEXISTS", m.key, :name)
|
105
|
+
end
|
106
|
+
|
107
|
+
test "assign attributes from the hash" do
|
108
|
+
event = Event.new(:name => "Ruby Tuesday")
|
109
|
+
assert event.name == "Ruby Tuesday"
|
110
|
+
end
|
111
|
+
|
112
|
+
test "assign an ID and save the object" do
|
113
|
+
event1 = Event.create(name: "Ruby Tuesday")
|
114
|
+
event2 = Event.create(name: "Ruby Meetup")
|
115
|
+
|
116
|
+
assert_equal "1", event1.id
|
117
|
+
assert_equal "2", event2.id
|
118
|
+
end
|
119
|
+
|
120
|
+
test "updates attributes" do
|
121
|
+
event = Meetup.create(:name => "Ruby Tuesday")
|
122
|
+
event.update(:name => "Ruby Meetup")
|
123
|
+
assert "Ruby Meetup" == event.name
|
124
|
+
end
|
125
|
+
|
126
|
+
test "reload attributes" do
|
127
|
+
event1 = Meetup.create(:name => "Foo", :location => "Bar")
|
128
|
+
event2 = Meetup[event1.id]
|
129
|
+
|
130
|
+
assert_equal "Foo", event1.name
|
131
|
+
assert_equal "Bar", event1.location
|
132
|
+
|
133
|
+
assert_equal "Foo", event2.name
|
134
|
+
assert_equal "Bar", event2.location
|
135
|
+
|
136
|
+
event1.update(:name => nil)
|
137
|
+
event2.load!
|
138
|
+
|
139
|
+
assert_equal nil, event1.name
|
140
|
+
assert_equal "Bar", event1.location
|
141
|
+
|
142
|
+
assert_equal nil, event2.name
|
143
|
+
assert_equal "Bar", event2.location
|
144
|
+
end
|
145
|
+
|
146
|
+
test "save the attributes in UTF8" do
|
147
|
+
event = Meetup.create(:name => "32° Kisei-sen")
|
148
|
+
assert "32° Kisei-sen" == Meetup[event.id].name
|
149
|
+
end
|
150
|
+
|
151
|
+
test "delete the attribute if set to nil" do
|
152
|
+
event = Meetup.create(:name => "Ruby Tuesday", :location => "Los Angeles")
|
153
|
+
assert "Los Angeles" == Meetup[event.id].location
|
154
|
+
assert event.update(:location => nil)
|
155
|
+
assert_equal nil, Meetup[event.id].location
|
156
|
+
end
|
157
|
+
|
158
|
+
test "not raise if an attribute is redefined" do
|
159
|
+
class RedefinedModel < Ohm::Model
|
160
|
+
attribute :name
|
161
|
+
|
162
|
+
silence_warnings do
|
163
|
+
attribute :name
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
test "not raise if a counter is redefined" do
|
169
|
+
class RedefinedModel < Ohm::Model
|
170
|
+
counter :age
|
171
|
+
|
172
|
+
silence_warnings do
|
173
|
+
counter :age
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
test "not raise if a set is redefined" do
|
179
|
+
class RedefinedModel < Ohm::Model
|
180
|
+
set :friends, lambda { }
|
181
|
+
|
182
|
+
silence_warnings do
|
183
|
+
set :friends, lambda { }
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
test "not raise if a collection is redefined" do
|
189
|
+
class RedefinedModel < Ohm::Model
|
190
|
+
set :toys, lambda { }
|
191
|
+
|
192
|
+
silence_warnings do
|
193
|
+
set :toys, lambda { }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
test "not raise if a index is redefined" do
|
199
|
+
class RedefinedModel < Ohm::Model
|
200
|
+
attribute :color
|
201
|
+
index :color
|
202
|
+
index :color
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
test "allow arbitrary IDs" do
|
207
|
+
Event.create(:id => "abc123", :name => "Concert")
|
208
|
+
|
209
|
+
assert Event.all.size == 1
|
210
|
+
assert Event["abc123"].name == "Concert"
|
211
|
+
end
|
212
|
+
|
213
|
+
test "forbid assignment of IDs on a new object" do
|
214
|
+
event = Event.new(:name => "Concert")
|
215
|
+
|
216
|
+
assert_raise(NoMethodError) do
|
217
|
+
event.id = "abc123"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
setup do
|
222
|
+
Ohm.redis.call("SADD", "Event:all", 1)
|
223
|
+
Ohm.redis.call("HSET", "Event:1", "name", "Concert")
|
224
|
+
end
|
225
|
+
|
226
|
+
test "return an instance of Event" do
|
227
|
+
assert Event[1].kind_of?(Event)
|
228
|
+
assert 1 == Event[1].id
|
229
|
+
assert "Concert" == Event[1].name
|
230
|
+
end
|
231
|
+
|
232
|
+
setup do
|
233
|
+
Ohm.redis.call("SADD", "User:all", 1)
|
234
|
+
Ohm.redis.call("HSET", "User:1", "email", "albert@example.com")
|
235
|
+
end
|
236
|
+
|
237
|
+
test "return an instance of User" do
|
238
|
+
assert User[1].kind_of?(User)
|
239
|
+
assert 1 == User[1].id
|
240
|
+
assert "albert@example.com" == User[1].email
|
241
|
+
end
|
242
|
+
|
243
|
+
test "allow to map key to models" do
|
244
|
+
assert [User[1]] == [1].map(&User)
|
245
|
+
end
|
246
|
+
|
247
|
+
setup do
|
248
|
+
Ohm.redis.call("SADD", "User:all", 1)
|
249
|
+
Ohm.redis.call("SET", "User:1:email", "albert@example.com")
|
250
|
+
|
251
|
+
@user = User[1]
|
252
|
+
end
|
253
|
+
|
254
|
+
test "change its attributes" do
|
255
|
+
@user.email = "maria@example.com"
|
256
|
+
assert "maria@example.com" == @user.email
|
257
|
+
end
|
258
|
+
|
259
|
+
test "save the new values" do
|
260
|
+
@user.email = "maria@example.com"
|
261
|
+
@user.save
|
262
|
+
|
263
|
+
@user.email = "maria@example.com"
|
264
|
+
@user.save
|
265
|
+
|
266
|
+
assert "maria@example.com" == User[1].email
|
267
|
+
end
|
268
|
+
|
269
|
+
test "assign a new id to the event" do
|
270
|
+
event1 = Event.new
|
271
|
+
event1.save
|
272
|
+
|
273
|
+
event2 = Event.new
|
274
|
+
event2.save
|
275
|
+
|
276
|
+
assert !event1.new?
|
277
|
+
assert !event2.new?
|
278
|
+
|
279
|
+
assert_equal "1", event1.id
|
280
|
+
assert_equal "2", event2.id
|
281
|
+
end
|
282
|
+
|
283
|
+
# Saving a model
|
284
|
+
test "create the model if it is new" do
|
285
|
+
event = Event.new(:name => "Foo").save
|
286
|
+
assert "Foo" == Event[event.id].name
|
287
|
+
end
|
288
|
+
|
289
|
+
test "save it only if it was previously created" do
|
290
|
+
event = Event.new
|
291
|
+
event.name = "Lorem ipsum"
|
292
|
+
event.save
|
293
|
+
|
294
|
+
event.name = "Lorem"
|
295
|
+
event.save
|
296
|
+
|
297
|
+
assert "Lorem" == Event[event.id].name
|
298
|
+
end
|
299
|
+
|
300
|
+
test "allow to hook into save" do
|
301
|
+
event = Event.create(:name => "Foo")
|
302
|
+
|
303
|
+
assert "foo" == event.slug
|
304
|
+
end
|
305
|
+
|
306
|
+
test "save counters" do
|
307
|
+
event = Event.create(:name => "Foo")
|
308
|
+
|
309
|
+
event.increment(:votes)
|
310
|
+
event.save
|
311
|
+
|
312
|
+
assert_equal 1, Event[event.id].votes
|
313
|
+
end
|
314
|
+
|
315
|
+
# Delete
|
316
|
+
test "delete an existing model" do
|
317
|
+
class ModelToBeDeleted < Ohm::Model
|
318
|
+
attribute :name
|
319
|
+
set :foos, :Post
|
320
|
+
set :bars, :Post
|
321
|
+
end
|
322
|
+
|
323
|
+
@model = ModelToBeDeleted.create(:name => "Lorem")
|
324
|
+
|
325
|
+
@model.foos.add(Post.create)
|
326
|
+
@model.bars.add(Post.create)
|
327
|
+
|
328
|
+
id = @model.id
|
329
|
+
|
330
|
+
@model.delete
|
331
|
+
|
332
|
+
assert Ohm.redis.call("GET", ModelToBeDeleted.key[id]).nil?
|
333
|
+
assert Ohm.redis.call("GET", ModelToBeDeleted.key[id][:name]).nil?
|
334
|
+
assert Array.new == Ohm.redis.call("SMEMBERS", ModelToBeDeleted.key[id][:foos])
|
335
|
+
assert Array.new == Ohm.redis.call("LRANGE", ModelToBeDeleted.key[id][:bars], 0, -1)
|
336
|
+
|
337
|
+
assert ModelToBeDeleted.all.empty?
|
338
|
+
end
|
339
|
+
|
340
|
+
setup do
|
341
|
+
end
|
342
|
+
|
343
|
+
test "no leftover keys" do
|
344
|
+
class ::Foo < Ohm::Model
|
345
|
+
attribute :name
|
346
|
+
index :name
|
347
|
+
track :notes
|
348
|
+
end
|
349
|
+
|
350
|
+
assert_equal [], Ohm.redis.call("KEYS", "*")
|
351
|
+
|
352
|
+
Foo.create(:name => "Bar")
|
353
|
+
expected = %w[Foo:1:_indices Foo:1 Foo:all Foo:id Foo:indices:name:Bar]
|
354
|
+
|
355
|
+
assert_equal expected.sort, Ohm.redis.call("KEYS", "*").sort
|
356
|
+
|
357
|
+
Foo[1].delete
|
358
|
+
assert ["Foo:id"] == Ohm.redis.call("KEYS", "*")
|
359
|
+
|
360
|
+
Foo.create(:name => "Baz")
|
361
|
+
|
362
|
+
Ohm.redis.call("SET", Foo[2].key[:notes], "something")
|
363
|
+
|
364
|
+
expected = %w[Foo:2:_indices Foo:2 Foo:all Foo:id
|
365
|
+
Foo:indices:name:Baz Foo:2:notes]
|
366
|
+
|
367
|
+
assert_equal expected.sort, Ohm.redis.call("KEYS", "*").sort
|
368
|
+
|
369
|
+
Foo[2].delete
|
370
|
+
assert ["Foo:id"] == Ohm.redis.call("KEYS", "*")
|
371
|
+
end
|
372
|
+
|
373
|
+
# Listing
|
374
|
+
test "find all" do
|
375
|
+
event1 = Event.new
|
376
|
+
event1.name = "Ruby Meetup"
|
377
|
+
event1.save
|
378
|
+
|
379
|
+
event2 = Event.new
|
380
|
+
event2.name = "Ruby Tuesday"
|
381
|
+
event2.save
|
382
|
+
|
383
|
+
all = Event.all
|
384
|
+
assert all.detect {|e| e.name == "Ruby Meetup" }
|
385
|
+
assert all.detect {|e| e.name == "Ruby Tuesday" }
|
386
|
+
end
|
387
|
+
|
388
|
+
# Fetching
|
389
|
+
test "fetch ids" do
|
390
|
+
event1 = Event.create(:name => "A")
|
391
|
+
event2 = Event.create(:name => "B")
|
392
|
+
|
393
|
+
assert_equal [event1, event2], Event.fetch([event1.id, event2.id])
|
394
|
+
end
|
395
|
+
|
396
|
+
# Sorting
|
397
|
+
test "sort all" do
|
398
|
+
Person.create :name => "D"
|
399
|
+
Person.create :name => "C"
|
400
|
+
Person.create :name => "B"
|
401
|
+
Person.create :name => "A"
|
402
|
+
|
403
|
+
names = Person.all.sort_by(:name, :order => "ALPHA").map { |p| p.name }
|
404
|
+
assert %w[A B C D] == names
|
405
|
+
end
|
406
|
+
|
407
|
+
test "return an empty array if there are no elements to sort" do
|
408
|
+
assert [] == Person.all.sort_by(:name)
|
409
|
+
end
|
410
|
+
|
411
|
+
test "return the first element sorted by id when using first" do
|
412
|
+
Person.create :name => "A"
|
413
|
+
Person.create :name => "B"
|
414
|
+
assert "A" == Person.all.first.name
|
415
|
+
end
|
416
|
+
|
417
|
+
test "return the first element sorted by name if first receives a sorting option" do
|
418
|
+
Person.create :name => "B"
|
419
|
+
Person.create :name => "A"
|
420
|
+
assert "A" == Person.all.first(:by => :name, :order => "ALPHA").name
|
421
|
+
end
|
422
|
+
|
423
|
+
test "return attribute values when the get parameter is specified" do
|
424
|
+
Person.create :name => "B"
|
425
|
+
Person.create :name => "A"
|
426
|
+
|
427
|
+
res = Person.all.sort_by(:name, :get => :name, :order => "ALPHA")
|
428
|
+
|
429
|
+
assert_equal ["A", "B"], res
|
430
|
+
end
|
431
|
+
|
432
|
+
test "work on lists" do
|
433
|
+
post = Post.create :body => "Hello world!"
|
434
|
+
|
435
|
+
redis = Post.redis
|
436
|
+
|
437
|
+
redis.call("RPUSH", post.related.key, Post.create(:body => "C").id)
|
438
|
+
redis.call("RPUSH", post.related.key, Post.create(:body => "B").id)
|
439
|
+
redis.call("RPUSH", post.related.key, Post.create(:body => "A").id)
|
440
|
+
|
441
|
+
res = post.related.sort_by(:body, :order => "ALPHA ASC").map { |r| r.body }
|
442
|
+
assert_equal ["A", "B", "C"], res
|
443
|
+
end
|
444
|
+
|
445
|
+
# Loading attributes
|
446
|
+
setup do
|
447
|
+
event = Event.new
|
448
|
+
event.name = "Ruby Tuesday"
|
449
|
+
event.save.id
|
450
|
+
end
|
451
|
+
|
452
|
+
test "load attributes as a strings" do
|
453
|
+
event = Event.create(:name => 1)
|
454
|
+
|
455
|
+
assert "1" == Event[event.id].name
|
456
|
+
end
|
457
|
+
|
458
|
+
# Enumerable indices
|
459
|
+
class Entry < Ohm::Model
|
460
|
+
attribute :tags
|
461
|
+
index :tag
|
462
|
+
|
463
|
+
def tag
|
464
|
+
tags.split(/\s+/)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
setup do
|
469
|
+
Entry.create(:tags => "foo bar baz")
|
470
|
+
end
|
471
|
+
|
472
|
+
test "finding by one entry in the enumerable" do |entry|
|
473
|
+
assert Entry.find(:tag => "foo").include?(entry)
|
474
|
+
assert Entry.find(:tag => "bar").include?(entry)
|
475
|
+
assert Entry.find(:tag => "baz").include?(entry)
|
476
|
+
end
|
477
|
+
|
478
|
+
test "finding by multiple entries in the enumerable" do |entry|
|
479
|
+
assert Entry.find(:tag => ["foo", "bar"]).include?(entry)
|
480
|
+
assert Entry.find(:tag => ["bar", "baz"]).include?(entry)
|
481
|
+
assert Entry.find(:tag => ["baz", "oof"]).empty?
|
482
|
+
end
|
483
|
+
|
484
|
+
# Attributes of type Set
|
485
|
+
setup do
|
486
|
+
@person1 = Person.create(:name => "Albert")
|
487
|
+
@person2 = Person.create(:name => "Bertrand")
|
488
|
+
@person3 = Person.create(:name => "Charles")
|
489
|
+
|
490
|
+
@event = Event.new
|
491
|
+
@event.name = "Ruby Tuesday"
|
492
|
+
end
|
493
|
+
|
494
|
+
test "filter elements" do
|
495
|
+
@event.save
|
496
|
+
@event.attendees.add(@person1)
|
497
|
+
@event.attendees.add(@person2)
|
498
|
+
|
499
|
+
assert [@person1] == @event.attendees.find(:initial => "A").to_a
|
500
|
+
assert [@person2] == @event.attendees.find(:initial => "B").to_a
|
501
|
+
assert [] == @event.attendees.find(:initial => "Z").to_a
|
502
|
+
end
|
503
|
+
|
504
|
+
test "delete elements" do
|
505
|
+
@event.save
|
506
|
+
@event.attendees.add(@person1)
|
507
|
+
@event.attendees.add(@person2)
|
508
|
+
|
509
|
+
assert_equal 2, @event.attendees.size
|
510
|
+
|
511
|
+
@event.attendees.delete(@person2)
|
512
|
+
assert_equal 1, @event.attendees.size
|
513
|
+
end
|
514
|
+
|
515
|
+
test "not be available if the model is new" do
|
516
|
+
assert_raise Ohm::MissingID do
|
517
|
+
@event.attendees
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
test "remove an element if sent delete" do
|
522
|
+
@event.save
|
523
|
+
@event.attendees.add(@person1)
|
524
|
+
@event.attendees.add(@person2)
|
525
|
+
@event.attendees.add(@person3)
|
526
|
+
|
527
|
+
assert_equal ["1", "2", "3"], Event.redis.call("SORT", @event.attendees.key)
|
528
|
+
|
529
|
+
Event.redis.call("SREM", @event.attendees.key, @person2.id)
|
530
|
+
assert_equal ["1", "3"], Event.redis.call("SORT", Event[@event.id].attendees.key)
|
531
|
+
end
|
532
|
+
|
533
|
+
test "return true if the set includes some member" do
|
534
|
+
@event.save
|
535
|
+
@event.attendees.add(@person1)
|
536
|
+
@event.attendees.add(@person2)
|
537
|
+
assert @event.attendees.include?(@person2)
|
538
|
+
|
539
|
+
@event.attendees.include?(@person3)
|
540
|
+
assert !@event.attendees.include?(@person3)
|
541
|
+
end
|
542
|
+
|
543
|
+
test "return instances of the passed model" do
|
544
|
+
@event.save
|
545
|
+
@event.attendees.add(@person1)
|
546
|
+
|
547
|
+
assert [@person1] == @event.attendees.to_a
|
548
|
+
assert @person1 == @event.attendees[@person1.id]
|
549
|
+
end
|
550
|
+
|
551
|
+
test "return the size of the set" do
|
552
|
+
@event.save
|
553
|
+
@event.attendees.add(@person1)
|
554
|
+
@event.attendees.add(@person2)
|
555
|
+
@event.attendees.add(@person3)
|
556
|
+
assert 3 == @event.attendees.size
|
557
|
+
end
|
558
|
+
|
559
|
+
test "empty the set" do
|
560
|
+
@event.save
|
561
|
+
@event.attendees.add(@person1)
|
562
|
+
|
563
|
+
Event.redis.call("DEL", @event.attendees.key)
|
564
|
+
|
565
|
+
assert @event.attendees.empty?
|
566
|
+
end
|
567
|
+
|
568
|
+
test "replace the values in the set" do
|
569
|
+
@event.save
|
570
|
+
@event.attendees.add(@person1)
|
571
|
+
|
572
|
+
assert [@person1] == @event.attendees.to_a
|
573
|
+
|
574
|
+
@event.attendees.replace([@person2, @person3])
|
575
|
+
|
576
|
+
assert [@person2, @person3] == @event.attendees.to_a.sort_by(&:id)
|
577
|
+
end
|
578
|
+
|
579
|
+
# Sorting lists and sets by model attributes
|
580
|
+
setup do
|
581
|
+
@event = Event.create(:name => "Ruby Tuesday")
|
582
|
+
{'D' => 4, 'C' => 2, 'B' => 5, 'A' => 3}.each_pair do |name, logins|
|
583
|
+
person = Person.create(:name => name)
|
584
|
+
person.increment :logins, logins
|
585
|
+
@event.attendees.add(person)
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
test "sort the model instances by the values provided" do
|
590
|
+
people = @event.attendees.sort_by(:name, :order => "ALPHA")
|
591
|
+
assert %w[A B C D] == people.map(&:name)
|
592
|
+
end
|
593
|
+
|
594
|
+
test "accept a number in the limit parameter" do
|
595
|
+
people = @event.attendees.sort_by(:name, :limit => [0, 2], :order => "ALPHA")
|
596
|
+
assert %w[A B] == people.map(&:name)
|
597
|
+
end
|
598
|
+
|
599
|
+
test "use the start parameter as an offset if the limit is provided" do
|
600
|
+
people = @event.attendees.sort_by(:name, :limit => [1, 2], :order => "ALPHA")
|
601
|
+
assert %w[B C] == people.map(&:name)
|
602
|
+
end
|
603
|
+
|
604
|
+
test "use counter attributes for sorting" do
|
605
|
+
people = @event.attendees.sort_by(:logins, :limit => [0, 3], :order => "ALPHA")
|
606
|
+
assert %w[C A D] == people.map(&:name)
|
607
|
+
end
|
608
|
+
|
609
|
+
test "use counter attributes for sorting with key option" do
|
610
|
+
people = @event.attendees.sort_by(:logins, :get => :logins, :limit => [0, 3], :order => "ALPHA")
|
611
|
+
assert %w[2 3 4] == people
|
612
|
+
end
|
613
|
+
|
614
|
+
# Collections initialized with a Model parameter
|
615
|
+
setup do
|
616
|
+
@user = User.create(:email => "albert@example.com")
|
617
|
+
@user.posts.add(Post.create(:body => "D"))
|
618
|
+
@user.posts.add(Post.create(:body => "C"))
|
619
|
+
@user.posts.add(Post.create(:body => "B"))
|
620
|
+
@user.posts.add(Post.create(:body => "A"))
|
621
|
+
end
|
622
|
+
|
623
|
+
test "return instances of the passed model" do
|
624
|
+
assert Post == @user.posts.first.class
|
625
|
+
end
|
626
|
+
|
627
|
+
test "remove an object from the set" do
|
628
|
+
post = @user.posts.first
|
629
|
+
assert @user.posts.include?(post)
|
630
|
+
|
631
|
+
User.redis.call("SREM", @user.posts.key, post.id)
|
632
|
+
assert !@user.posts.include?(post)
|
633
|
+
end
|
634
|
+
|
635
|
+
test "remove an object id from the set" do
|
636
|
+
post = @user.posts.first
|
637
|
+
assert @user.posts.include?(post)
|
638
|
+
|
639
|
+
User.redis.call("SREM", @user.posts.key, post.id)
|
640
|
+
assert !@user.posts.include?(post)
|
641
|
+
end
|
642
|
+
|
643
|
+
# Counters
|
644
|
+
setup do
|
645
|
+
@event = Event.create(:name => "Ruby Tuesday")
|
646
|
+
end
|
647
|
+
|
648
|
+
test "be zero if not initialized" do
|
649
|
+
assert 0 == @event.votes
|
650
|
+
end
|
651
|
+
|
652
|
+
test "be able to increment a counter" do
|
653
|
+
@event.increment(:votes)
|
654
|
+
assert 1 == @event.votes
|
655
|
+
|
656
|
+
@event.increment(:votes, 2)
|
657
|
+
assert 3 == @event.votes
|
658
|
+
end
|
659
|
+
|
660
|
+
test "be able to decrement a counter" do
|
661
|
+
@event.decrement(:votes)
|
662
|
+
assert @event.votes == -1
|
663
|
+
|
664
|
+
@event.decrement(:votes, 2)
|
665
|
+
assert @event.votes == -3
|
666
|
+
end
|
667
|
+
|
668
|
+
# Comparison
|
669
|
+
setup do
|
670
|
+
@user = User.create(:email => "foo")
|
671
|
+
end
|
672
|
+
|
673
|
+
test "be comparable to other instances" do
|
674
|
+
assert @user == User[@user.id]
|
675
|
+
|
676
|
+
assert @user != User.create
|
677
|
+
assert User.new != User.new
|
678
|
+
end
|
679
|
+
|
680
|
+
test "not be comparable to instances of other models" do
|
681
|
+
assert @user != Event.create(:name => "Ruby Tuesday")
|
682
|
+
end
|
683
|
+
|
684
|
+
test "be comparable to non-models" do
|
685
|
+
assert @user != 1
|
686
|
+
assert @user != true
|
687
|
+
|
688
|
+
# Not equal although the other object responds to #key.
|
689
|
+
assert @user != OpenStruct.new(:key => @user.send(:key))
|
690
|
+
end
|
691
|
+
|
692
|
+
# Debugging
|
693
|
+
class ::Bar < Ohm::Model
|
694
|
+
attribute :name
|
695
|
+
counter :visits
|
696
|
+
set :friends, self
|
697
|
+
set :comments, self
|
698
|
+
|
699
|
+
def foo
|
700
|
+
bar.foo
|
701
|
+
end
|
702
|
+
|
703
|
+
def baz
|
704
|
+
bar.new.foo
|
705
|
+
end
|
706
|
+
|
707
|
+
def bar
|
708
|
+
SomeMissingConstant
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
# Models connected to different databases
|
713
|
+
class ::Car < Ohm::Model
|
714
|
+
attribute :name
|
715
|
+
|
716
|
+
self.redis = Redic.new
|
717
|
+
end
|
718
|
+
|
719
|
+
class ::Make < Ohm::Model
|
720
|
+
attribute :name
|
721
|
+
end
|
722
|
+
|
723
|
+
setup do
|
724
|
+
Car.redis.call("SELECT", 15)
|
725
|
+
end
|
726
|
+
|
727
|
+
test "save to the selected database" do
|
728
|
+
car = Car.create(:name => "Twingo")
|
729
|
+
make = Make.create(:name => "Renault")
|
730
|
+
|
731
|
+
redis = Redic.new
|
732
|
+
|
733
|
+
assert ["1"] == redis.call("SMEMBERS", "Make:all")
|
734
|
+
assert [] == redis.call("SMEMBERS", "Car:all")
|
735
|
+
|
736
|
+
assert ["1"] == Car.redis.call("SMEMBERS", "Car:all")
|
737
|
+
assert [] == Car.redis.call("SMEMBERS", "Make:all")
|
738
|
+
|
739
|
+
assert car == Car[1]
|
740
|
+
assert make == Make[1]
|
741
|
+
|
742
|
+
Make.redis.call("FLUSHDB")
|
743
|
+
|
744
|
+
assert car == Car[1]
|
745
|
+
assert Make[1].nil?
|
746
|
+
end
|
747
|
+
|
748
|
+
test "allow changing the database" do
|
749
|
+
Car.create(:name => "Twingo")
|
750
|
+
assert_equal ["1"], Car.redis.call("SMEMBERS", Car.all.key)
|
751
|
+
|
752
|
+
Car.redis = Redic.new("redis://127.0.0.1:6379")
|
753
|
+
assert_equal [], Car.redis.call("SMEMBERS", Car.all.key)
|
754
|
+
|
755
|
+
Car.redis.call("SELECT", 15)
|
756
|
+
assert_equal ["1"], Car.redis.call("SMEMBERS", Car.all.key)
|
757
|
+
end
|
758
|
+
|
759
|
+
# Persistence
|
760
|
+
test "persist attributes to a hash" do
|
761
|
+
event = Event.create(:name => "Redis Meetup")
|
762
|
+
event.increment(:votes)
|
763
|
+
|
764
|
+
assert "hash" == Ohm.redis.call("TYPE", "Event:1")
|
765
|
+
|
766
|
+
expected= %w[Event:1 Event:1:counters Event:all Event:id]
|
767
|
+
assert_equal expected, Ohm.redis.call("KEYS", "Event:*").sort
|
768
|
+
|
769
|
+
assert "Redis Meetup" == Event[1].name
|
770
|
+
assert 1 == Event[1].votes
|
771
|
+
end
|
772
|
+
|
773
|
+
# namespaced models
|
774
|
+
test "be persisted" do
|
775
|
+
SomeNamespace::Foo.create(:name => "foo")
|
776
|
+
|
777
|
+
SomeNamespace::Bar.create(:foo => SomeNamespace::Foo[1])
|
778
|
+
|
779
|
+
assert "hash" == Ohm.redis.call("TYPE", "SomeNamespace::Foo:1")
|
780
|
+
|
781
|
+
assert "foo" == SomeNamespace::Foo[1].name
|
782
|
+
assert "foo" == SomeNamespace::Bar[1].foo.name
|
783
|
+
end if RUBY_VERSION >= "2.0.0"
|
784
|
+
|
785
|
+
test "typecast attributes" do
|
786
|
+
class Option < Ohm::Model
|
787
|
+
attribute :votes, lambda { |x| x.to_i }
|
788
|
+
end
|
789
|
+
|
790
|
+
option = Option.create :votes => 20
|
791
|
+
option.update(:votes => option.votes + 1)
|
792
|
+
|
793
|
+
assert_equal 21, option.votes
|
794
|
+
end
|
795
|
+
|
796
|
+
test "poster-example for overriding writers" do
|
797
|
+
silence_warnings do
|
798
|
+
class Advertiser < Ohm::Model
|
799
|
+
attribute :email
|
800
|
+
|
801
|
+
def email=(e)
|
802
|
+
attributes[:email] = e.to_s.downcase.strip
|
803
|
+
end
|
804
|
+
end
|
805
|
+
end
|
806
|
+
|
807
|
+
a = Advertiser.new(:email => " FOO@BAR.COM ")
|
808
|
+
assert_equal "foo@bar.com", a.email
|
809
|
+
end
|
810
|
+
|
811
|
+
test "scripts are flushed" do
|
812
|
+
m = Meetup.create(:name => "Foo")
|
813
|
+
|
814
|
+
Meetup.redis.call("SCRIPT", "FLUSH")
|
815
|
+
|
816
|
+
m.update(:name => "Bar")
|
817
|
+
|
818
|
+
assert_equal m.name, Meetup[m.id].name
|
819
|
+
end
|