ohm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.markdown +74 -0
- data/Rakefile +78 -0
- data/lib/ohm.rb +242 -0
- data/test/all_tests.rb +2 -0
- data/test/benchmarks.rb +32 -0
- data/test/db/dump.rdb +0 -0
- data/test/db/redis.pid +1 -0
- data/test/model_test.rb +244 -0
- data/test/test.conf +15 -0
- data/test/test_helper.rb +7 -0
- data/test/validations_test.rb +152 -0
- metadata +75 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Michel Martens and Damian Janowski
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
Ohm
|
2
|
+
============
|
3
|
+
|
4
|
+
Object-hash mapping library for Redis.
|
5
|
+
|
6
|
+
Description
|
7
|
+
-----------
|
8
|
+
|
9
|
+
Ohm is a library that allows to store an object in
|
10
|
+
[Redis](http://code.google.com/p/redis/), a persistent key-value
|
11
|
+
database. It includes an extensible list of validations and has very
|
12
|
+
good performance.
|
13
|
+
|
14
|
+
Usage
|
15
|
+
-----
|
16
|
+
|
17
|
+
require 'ohm'
|
18
|
+
|
19
|
+
$redis = Redis.new
|
20
|
+
|
21
|
+
class Event < Ohm::Model
|
22
|
+
attribute :name
|
23
|
+
set :participants
|
24
|
+
|
25
|
+
def validate
|
26
|
+
assert_present :name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
event = Event.create(:name => "Ruby Tuesday")
|
31
|
+
event.participants << "Michel Martens"
|
32
|
+
event.participants << "Damian Janowski"
|
33
|
+
|
34
|
+
another_event = Event.new
|
35
|
+
another_event.valid? #=> false
|
36
|
+
another_event.errors #=> [[:name, :nil]]
|
37
|
+
|
38
|
+
another_event.name = ""
|
39
|
+
another_event.valid? #=> false
|
40
|
+
another_event.errors #=> [[:name, :empty]]
|
41
|
+
|
42
|
+
another_event.name = "Ruby Lunch"
|
43
|
+
another_event.save #=> true
|
44
|
+
|
45
|
+
Installation
|
46
|
+
------------
|
47
|
+
|
48
|
+
$ sudo gem install ohm
|
49
|
+
|
50
|
+
License
|
51
|
+
-------
|
52
|
+
|
53
|
+
Copyright (c) 2009 Michel Martens and Damian Janowski
|
54
|
+
|
55
|
+
Permission is hereby granted, free of charge, to any person
|
56
|
+
obtaining a copy of this software and associated documentation
|
57
|
+
files (the "Software"), to deal in the Software without
|
58
|
+
restriction, including without limitation the rights to use,
|
59
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
60
|
+
copies of the Software, and to permit persons to whom the
|
61
|
+
Software is furnished to do so, subject to the following
|
62
|
+
conditions:
|
63
|
+
|
64
|
+
The above copyright notice and this permission notice shall be
|
65
|
+
included in all copies or substantial portions of the Software.
|
66
|
+
|
67
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
68
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
69
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
70
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
71
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
72
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
73
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
74
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require "rake/testtask"
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
def gem_spec_file
|
8
|
+
'ohm.gemspec'
|
9
|
+
end
|
10
|
+
|
11
|
+
def gem_spec
|
12
|
+
$gem_spec ||= eval(File.read(gem_spec_file)) rescue nil
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Run all tests'
|
16
|
+
Rake::TestTask.new(:test) do |t|
|
17
|
+
t.pattern = 'test/**/*_test.rb'
|
18
|
+
t.verbose = false
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::GemPackageTask.new(gem_spec) do |pkg|
|
22
|
+
pkg.need_zip = false
|
23
|
+
pkg.need_tar = false
|
24
|
+
rm_f FileList['pkg/**/*.*']
|
25
|
+
end if gem_spec
|
26
|
+
|
27
|
+
desc "Generate the gemspec file."
|
28
|
+
task :gemspec do
|
29
|
+
require 'erb'
|
30
|
+
|
31
|
+
File.open(gem_spec_file, 'w') do |f|
|
32
|
+
f.write ERB.new(File.read("#{gem_spec_file}.erb")).result(binding)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Builds and installs the gem."
|
37
|
+
task :install => :repackage do
|
38
|
+
`sudo gem install pkg/#{gem_spec.name}-#{gem_spec.version}.gem`
|
39
|
+
end
|
40
|
+
|
41
|
+
def package(ext='')
|
42
|
+
"pkg/ohm-#{gem_spec.version}" + ext
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'Build packages'
|
46
|
+
task :package => %w[.gem .tar.gz].map {|e| package(e)}
|
47
|
+
|
48
|
+
desc 'Build and install as local gem'
|
49
|
+
task :install => package('.gem') do
|
50
|
+
sh "gem install #{package('.gem')}"
|
51
|
+
end
|
52
|
+
|
53
|
+
directory 'pkg/'
|
54
|
+
CLOBBER.include('pkg')
|
55
|
+
|
56
|
+
file package('.gem') => %w[pkg/ ohm.gemspec] + gem_spec.files do |f|
|
57
|
+
sh "gem build ohm.gemspec"
|
58
|
+
mv File.basename(f.name), f.name
|
59
|
+
end
|
60
|
+
|
61
|
+
file package('.tar.gz') => %w[pkg/] + gem_spec.files do |f|
|
62
|
+
sh <<-SH
|
63
|
+
git archive \
|
64
|
+
--prefix=ohm-#{gem_spec.version}/ \
|
65
|
+
--format=tar \
|
66
|
+
HEAD | gzip > #{f.name}
|
67
|
+
SH
|
68
|
+
end
|
69
|
+
|
70
|
+
# Rubyforge Release / Publish Tasks ==================================
|
71
|
+
|
72
|
+
desc 'Publish gem and tarball to rubyforge'
|
73
|
+
task 'release' => [package('.gem'), package('.tar.gz')] do |t|
|
74
|
+
sh <<-end
|
75
|
+
rubyforge add_release ohm ohm #{gem_spec.version} #{package('.gem')} &&
|
76
|
+
rubyforge add_file ohm ohm #{gem_spec.version} #{package('.tar.gz')}
|
77
|
+
end
|
78
|
+
end
|
data/lib/ohm.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "redis"
|
3
|
+
|
4
|
+
module Ohm
|
5
|
+
module Validations
|
6
|
+
def valid?
|
7
|
+
errors.clear
|
8
|
+
validate
|
9
|
+
errors.empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate
|
13
|
+
end
|
14
|
+
|
15
|
+
def errors
|
16
|
+
@errors ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def assert_format(att, format)
|
22
|
+
if assert_present(att)
|
23
|
+
assert attribute(att).match(format), [att, :format]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def assert_present(att)
|
28
|
+
if assert_not_nil(att)
|
29
|
+
assert attribute(att).any?, [att, :empty]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def assert_not_nil(att)
|
34
|
+
assert attribute(att), [att, :nil]
|
35
|
+
end
|
36
|
+
|
37
|
+
def assert(value, error)
|
38
|
+
value or errors.push(error) && false
|
39
|
+
end
|
40
|
+
|
41
|
+
def attribute(att)
|
42
|
+
instance_variable_get("@#{att}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module Attributes
|
47
|
+
class Collection < Array
|
48
|
+
attr_accessor :key, :db
|
49
|
+
|
50
|
+
def initialize(db, key)
|
51
|
+
self.db = db
|
52
|
+
self.key = key
|
53
|
+
super(retrieve)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class List < Collection
|
58
|
+
def retrieve
|
59
|
+
db.list_range(key, 0, -1)
|
60
|
+
end
|
61
|
+
|
62
|
+
def << value
|
63
|
+
super(value) if db.push_tail(key, value)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Set < Collection
|
68
|
+
def retrieve
|
69
|
+
db.set_members(key).sort
|
70
|
+
end
|
71
|
+
|
72
|
+
def << value
|
73
|
+
super(value) if db.set_add(key, value)
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete(value)
|
77
|
+
super(value) if db.set_delete(key, value)
|
78
|
+
end
|
79
|
+
|
80
|
+
def include?(value)
|
81
|
+
db.set_member?(key, value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Model
|
87
|
+
include Validations
|
88
|
+
|
89
|
+
ModelIsNew = Class.new(StandardError)
|
90
|
+
|
91
|
+
@@attributes = Hash.new { |hash, key| hash[key] = [] }
|
92
|
+
@@collections = Hash.new { |hash, key| hash[key] = [] }
|
93
|
+
|
94
|
+
attr_accessor :id
|
95
|
+
|
96
|
+
def self.attribute(name)
|
97
|
+
attr_writer(name)
|
98
|
+
attr_value_reader(name)
|
99
|
+
attributes << name
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.list(name)
|
103
|
+
attr_list_reader(name)
|
104
|
+
collections << name
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.set(name)
|
108
|
+
attr_set_reader(name)
|
109
|
+
collections << name
|
110
|
+
end
|
111
|
+
|
112
|
+
# TODO Encapsulate access to db?
|
113
|
+
def self.attr_value_reader(name)
|
114
|
+
class_eval <<-EOS
|
115
|
+
def #{name}
|
116
|
+
@#{name} ||= db[key("#{name}")]
|
117
|
+
end
|
118
|
+
EOS
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.attr_list_reader(name)
|
122
|
+
class_eval <<-EOS
|
123
|
+
def #{name}
|
124
|
+
@#{name} ||= Attributes::List.new(db, key("#{name}"))
|
125
|
+
end
|
126
|
+
EOS
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.attr_set_reader(name)
|
130
|
+
class_eval <<-EOS
|
131
|
+
def #{name}
|
132
|
+
@#{name} ||= Attributes::Set.new(db, key("#{name}"))
|
133
|
+
end
|
134
|
+
EOS
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.[](id)
|
138
|
+
new(:id => id) if exists?(id)
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.all
|
142
|
+
filter(:all).map do |id|
|
143
|
+
new(:id => id)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.attributes
|
148
|
+
@@attributes[self]
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.collections
|
152
|
+
@@collections[self]
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.create(*args)
|
156
|
+
new(*args).create
|
157
|
+
end
|
158
|
+
|
159
|
+
def initialize(attrs = {})
|
160
|
+
attrs.each do |key, value|
|
161
|
+
send(:"#{key}=", value)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def create
|
166
|
+
return unless valid?
|
167
|
+
initialize_id
|
168
|
+
create_model_membership
|
169
|
+
save!
|
170
|
+
end
|
171
|
+
|
172
|
+
def save
|
173
|
+
return unless valid?
|
174
|
+
save!
|
175
|
+
end
|
176
|
+
|
177
|
+
def delete
|
178
|
+
delete_attributes(collections)
|
179
|
+
delete_attributes(attributes)
|
180
|
+
delete_model_membership
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
def attributes
|
185
|
+
self.class.attributes
|
186
|
+
end
|
187
|
+
|
188
|
+
def collections
|
189
|
+
self.class.collections
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def self.db
|
195
|
+
$redis
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.key(*args)
|
199
|
+
args.unshift(self).join(":")
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.filter(name)
|
203
|
+
db.set_members(key(name))
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.exists?(id)
|
207
|
+
db.set_member?(key(:all), id)
|
208
|
+
end
|
209
|
+
|
210
|
+
def initialize_id
|
211
|
+
self.id = db.incr(self.class.key("id"))
|
212
|
+
end
|
213
|
+
|
214
|
+
def db
|
215
|
+
self.class.db
|
216
|
+
end
|
217
|
+
|
218
|
+
def key(*args)
|
219
|
+
raise ModelIsNew unless id
|
220
|
+
self.class.key(id, *args)
|
221
|
+
end
|
222
|
+
|
223
|
+
def delete_attributes(atts)
|
224
|
+
atts.each do |att|
|
225
|
+
db.delete(key(att))
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def create_model_membership
|
230
|
+
db.set_add(self.class.key(:all), id)
|
231
|
+
end
|
232
|
+
|
233
|
+
def delete_model_membership
|
234
|
+
db.set_delete(self.class.key(:all), id)
|
235
|
+
end
|
236
|
+
|
237
|
+
def save!
|
238
|
+
attributes.each { |att| db[key(att)] = send(att) }
|
239
|
+
self
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
data/test/all_tests.rb
ADDED
data/test/benchmarks.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bench"
|
3
|
+
require File.dirname(__FILE__) + "/../lib/ohm"
|
4
|
+
|
5
|
+
$redis = Redis.new(:port => 6381)
|
6
|
+
$redis.flush_db
|
7
|
+
|
8
|
+
class Event < Ohm::Model
|
9
|
+
attribute :name
|
10
|
+
set :attendees
|
11
|
+
|
12
|
+
def validate
|
13
|
+
assert_present :name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
event = Event.create(:name => "Ruby Tuesday")
|
18
|
+
array = []
|
19
|
+
|
20
|
+
benchmark "redis add to set" do
|
21
|
+
$redis.set_add("foo", 1)
|
22
|
+
end
|
23
|
+
|
24
|
+
benchmark "ohm add to set" do
|
25
|
+
event.attendees << 1
|
26
|
+
end
|
27
|
+
|
28
|
+
benchmark "ruby array push" do
|
29
|
+
array.push(1)
|
30
|
+
end
|
31
|
+
|
32
|
+
run 10000
|
data/test/db/dump.rdb
ADDED
Binary file
|
data/test/db/redis.pid
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
6416
|
data/test/model_test.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class Event < Ohm::Model
|
4
|
+
attribute :name
|
5
|
+
set :attendees
|
6
|
+
end
|
7
|
+
|
8
|
+
class User < Ohm::Model
|
9
|
+
attribute :email
|
10
|
+
end
|
11
|
+
|
12
|
+
class Post < Ohm::Model
|
13
|
+
attribute :body
|
14
|
+
list :comments
|
15
|
+
end
|
16
|
+
|
17
|
+
class TestRedis < Test::Unit::TestCase
|
18
|
+
context "An event initialized with a hash of attributes" do
|
19
|
+
should "assign the passed attributes" do
|
20
|
+
event = Event.new(:name => "Ruby Tuesday")
|
21
|
+
assert_equal event.name, "Ruby Tuesday"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "An event created from a hash of attributes" do
|
26
|
+
should "assign an id and save the object" do
|
27
|
+
event1 = Event.create(:name => "Ruby Tuesday")
|
28
|
+
event2 = Event.create(:name => "Ruby Meetup")
|
29
|
+
assert_equal event1.id + 1, event2.id
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "Finding an event" do
|
34
|
+
setup do
|
35
|
+
$redis.set_add("Event", 1)
|
36
|
+
$redis["Event:1:name"] = "Concert"
|
37
|
+
end
|
38
|
+
|
39
|
+
should "return an instance of Event" do
|
40
|
+
assert Event[1].kind_of?(Event)
|
41
|
+
assert_equal 1, Event[1].id
|
42
|
+
assert_equal "Concert", Event[1].name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "Finding a user" do
|
47
|
+
setup do
|
48
|
+
$redis.set_add("User:all", 1)
|
49
|
+
$redis["User:1:email"] = "albert@example.com"
|
50
|
+
end
|
51
|
+
|
52
|
+
should "return an instance of User" do
|
53
|
+
assert User[1].kind_of?(User)
|
54
|
+
assert_equal 1, User[1].id
|
55
|
+
assert_equal "albert@example.com", User[1].email
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "Updating a user" do
|
60
|
+
setup do
|
61
|
+
$redis.set_add("User:all", 1)
|
62
|
+
$redis["User:1:email"] = "albert@example.com"
|
63
|
+
|
64
|
+
@user = User[1]
|
65
|
+
end
|
66
|
+
|
67
|
+
should "change its attributes" do
|
68
|
+
@user.email = "maria@example.com"
|
69
|
+
assert_equal "maria@example.com", @user.email
|
70
|
+
end
|
71
|
+
|
72
|
+
should "save the new values" do
|
73
|
+
@user.email = "maria@example.com"
|
74
|
+
@user.save
|
75
|
+
|
76
|
+
@user.email = "maria@example.com"
|
77
|
+
@user.save
|
78
|
+
|
79
|
+
assert_equal "maria@example.com", User[1].email
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "Creating a new model" do
|
84
|
+
should "assign a new id to the event" do
|
85
|
+
event1 = Event.new
|
86
|
+
event1.create
|
87
|
+
|
88
|
+
event2 = Event.new
|
89
|
+
event2.create
|
90
|
+
|
91
|
+
assert event1.id
|
92
|
+
assert_equal event1.id + 1, event2.id
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "Saving a model" do
|
97
|
+
should "not save a new model" do
|
98
|
+
assert_raise Ohm::Model::ModelIsNew do
|
99
|
+
Event.new.save
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
should "save it only if it was previously created" do
|
104
|
+
event = Event.new
|
105
|
+
event.name = "Lorem ipsum"
|
106
|
+
event.create
|
107
|
+
|
108
|
+
event.name = "Lorem"
|
109
|
+
event.save
|
110
|
+
|
111
|
+
assert_equal "Lorem", Event[event.id].name
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "Delete" do
|
116
|
+
class ModelToBeDeleted < Ohm::Model
|
117
|
+
attribute :name
|
118
|
+
set :foos
|
119
|
+
list :bars
|
120
|
+
end
|
121
|
+
|
122
|
+
setup do
|
123
|
+
@model = ModelToBeDeleted.create(:name => "Lorem")
|
124
|
+
|
125
|
+
@model.foos << "foo"
|
126
|
+
@model.bars << "bar"
|
127
|
+
end
|
128
|
+
|
129
|
+
should "delete an existing model" do
|
130
|
+
id = @model.id
|
131
|
+
|
132
|
+
@model.delete
|
133
|
+
|
134
|
+
assert_nil $redis[ModelToBeDeleted.key(id)]
|
135
|
+
assert_nil $redis[ModelToBeDeleted.key(id, :name)]
|
136
|
+
assert_equal Set.new, $redis.set_members(ModelToBeDeleted.key(id, :foos))
|
137
|
+
assert_equal Array.new, $redis.list_range(ModelToBeDeleted.key(id, :bars), 0, -1)
|
138
|
+
|
139
|
+
assert ModelToBeDeleted.all.empty?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context "Listing" do
|
144
|
+
should "find all" do
|
145
|
+
event1 = Event.new
|
146
|
+
event1.name = "Ruby Meetup"
|
147
|
+
event1.create
|
148
|
+
|
149
|
+
event2 = Event.new
|
150
|
+
event2.name = "Ruby Tuesday"
|
151
|
+
event2.create
|
152
|
+
|
153
|
+
all = Event.all
|
154
|
+
|
155
|
+
assert all.detect {|e| e.name == "Ruby Meetup" }
|
156
|
+
assert all.detect {|e| e.name == "Ruby Tuesday" }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "Loading attributes" do
|
161
|
+
setup do
|
162
|
+
event = Event.new
|
163
|
+
event.name = "Ruby Tuesday"
|
164
|
+
@id = event.create.id
|
165
|
+
end
|
166
|
+
|
167
|
+
should "load attributes lazily" do
|
168
|
+
event = Event[@id]
|
169
|
+
|
170
|
+
assert_nil event.send(:instance_variable_get, "@name")
|
171
|
+
assert_equal "Ruby Tuesday", event.name
|
172
|
+
end
|
173
|
+
|
174
|
+
should "load attributes as a strings" do
|
175
|
+
event = Event.create(:name => 1)
|
176
|
+
|
177
|
+
assert_equal "1", Event[event.id].name
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "Attributes of type Set" do
|
182
|
+
setup do
|
183
|
+
@event = Event.new
|
184
|
+
@event.name = "Ruby Tuesday"
|
185
|
+
end
|
186
|
+
|
187
|
+
should "not be available if the model is new" do
|
188
|
+
assert_raise Ohm::Model::ModelIsNew do
|
189
|
+
@event.attendees << 1
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
should "return an array if the model exists" do
|
194
|
+
@event.create
|
195
|
+
assert @event.attendees.kind_of?(Array)
|
196
|
+
end
|
197
|
+
|
198
|
+
should "remove an element if sent :delete" do
|
199
|
+
@event.create
|
200
|
+
@event.attendees << "1"
|
201
|
+
@event.attendees << "2"
|
202
|
+
@event.attendees << "3"
|
203
|
+
assert_equal ["1", "2", "3"], @event.attendees
|
204
|
+
@event.attendees.delete("2")
|
205
|
+
assert_equal ["1", "3"], Event[@event.id].attendees
|
206
|
+
end
|
207
|
+
|
208
|
+
should "return true if the set includes some member" do
|
209
|
+
@event.create
|
210
|
+
@event.attendees << "1"
|
211
|
+
@event.attendees << "2"
|
212
|
+
@event.attendees << "3"
|
213
|
+
assert @event.attendees.include?("2")
|
214
|
+
assert_equal false, @event.attendees.include?("4")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context "Attributes of type List" do
|
219
|
+
setup do
|
220
|
+
@post = Post.new
|
221
|
+
@post.body = "Hello world!"
|
222
|
+
@post.create
|
223
|
+
end
|
224
|
+
|
225
|
+
should "return an array" do
|
226
|
+
assert @post.comments.kind_of?(Array)
|
227
|
+
end
|
228
|
+
|
229
|
+
should "keep the inserting order" do
|
230
|
+
@post.comments << "1"
|
231
|
+
@post.comments << "2"
|
232
|
+
@post.comments << "3"
|
233
|
+
assert_equal ["1", "2", "3"], @post.comments
|
234
|
+
end
|
235
|
+
|
236
|
+
should "keep the inserting order after saving" do
|
237
|
+
@post.comments << "1"
|
238
|
+
@post.comments << "2"
|
239
|
+
@post.comments << "3"
|
240
|
+
@post.save
|
241
|
+
assert_equal ["1", "2", "3"], Post[@post.id].comments
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
data/test/test.conf
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ValidationsTest < Test::Unit::TestCase
|
4
|
+
class Event < Ohm::Model
|
5
|
+
attribute :name
|
6
|
+
|
7
|
+
def validate
|
8
|
+
assert_format(:name, /^\w+$/)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "A new model with validations" do
|
13
|
+
setup do
|
14
|
+
@event = Event.new
|
15
|
+
end
|
16
|
+
|
17
|
+
context "That must have a present name" do
|
18
|
+
should "not be created if the name is never assigned" do
|
19
|
+
@event.create
|
20
|
+
assert_nil @event.id
|
21
|
+
end
|
22
|
+
|
23
|
+
should "not be created if the name assigned is empty" do
|
24
|
+
@event.name = ""
|
25
|
+
@event.create
|
26
|
+
assert_nil @event.id
|
27
|
+
end
|
28
|
+
|
29
|
+
should "be created if the name assigned is not empty" do
|
30
|
+
@event.name = "hello"
|
31
|
+
@event.create
|
32
|
+
assert_not_nil @event.id
|
33
|
+
end
|
34
|
+
|
35
|
+
context "And must have a name with only \w+" do
|
36
|
+
should "not be created if the name doesn't match /^\w+$/" do
|
37
|
+
@event.name = "hello-world"
|
38
|
+
@event.create
|
39
|
+
assert_nil @event.id
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "An existing model with a valid name" do
|
46
|
+
setup do
|
47
|
+
@event = Event.create(:name => "original")
|
48
|
+
end
|
49
|
+
|
50
|
+
context "That has the name changed" do
|
51
|
+
should "not be saved if the new name is nil" do
|
52
|
+
@event.name = nil
|
53
|
+
@event.save
|
54
|
+
assert_equal false, @event.valid?
|
55
|
+
assert_equal "original", Event[@event.id].name
|
56
|
+
end
|
57
|
+
|
58
|
+
should "not be saved if the name assigned is empty" do
|
59
|
+
@event.name = ""
|
60
|
+
@event.save
|
61
|
+
assert_equal false, @event.valid?
|
62
|
+
assert_equal "original", Event[@event.id].name
|
63
|
+
end
|
64
|
+
|
65
|
+
should "be saved if the name assigned is not empty" do
|
66
|
+
@event.name = "hello"
|
67
|
+
@event.save
|
68
|
+
assert @event.valid?
|
69
|
+
assert_equal "hello", Event[@event.id].name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "Validations module" do
|
75
|
+
class Validatable
|
76
|
+
attr_accessor :name
|
77
|
+
|
78
|
+
include Ohm::Validations
|
79
|
+
end
|
80
|
+
|
81
|
+
setup do
|
82
|
+
@target = Validatable.new
|
83
|
+
end
|
84
|
+
|
85
|
+
context "assert" do
|
86
|
+
should "add errors to a collection" do
|
87
|
+
def @target.validate
|
88
|
+
assert(false, "Something bad")
|
89
|
+
end
|
90
|
+
|
91
|
+
@target.validate
|
92
|
+
|
93
|
+
assert_equal ["Something bad"], @target.errors
|
94
|
+
end
|
95
|
+
|
96
|
+
should "allow for nested validations" do
|
97
|
+
def @target.validate
|
98
|
+
if assert(true, "No error")
|
99
|
+
assert(false, "Chained error")
|
100
|
+
end
|
101
|
+
|
102
|
+
if assert(false, "Parent error")
|
103
|
+
assert(false, "No chained error")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
@target.validate
|
108
|
+
|
109
|
+
assert_equal ["Chained error", "Parent error"], @target.errors
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "assert_present" do
|
114
|
+
setup do
|
115
|
+
def @target.validate
|
116
|
+
assert_present(:name)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
should "fail when the attribute is nil" do
|
121
|
+
@target.validate
|
122
|
+
|
123
|
+
assert_equal [[:name, :nil]], @target.errors
|
124
|
+
end
|
125
|
+
|
126
|
+
should "fail when the attribute is empty" do
|
127
|
+
@target.name = ""
|
128
|
+
@target.validate
|
129
|
+
|
130
|
+
assert_equal [[:name, :empty]], @target.errors
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "assert_not_nil" do
|
135
|
+
should "fail when the attribute is nil" do
|
136
|
+
def @target.validate
|
137
|
+
assert_not_nil(:name)
|
138
|
+
end
|
139
|
+
|
140
|
+
@target.validate
|
141
|
+
|
142
|
+
assert_equal [[:name, :nil]], @target.errors
|
143
|
+
|
144
|
+
@target.errors.clear
|
145
|
+
@target.name = ""
|
146
|
+
@target.validate
|
147
|
+
|
148
|
+
assert_equal [], @target.errors
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ohm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michel Martens, Damian Janowski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-13 00:00:00 -02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: redis
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "1.0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: michel@soveran.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/ohm.rb
|
35
|
+
- README.markdown
|
36
|
+
- LICENSE
|
37
|
+
- Rakefile
|
38
|
+
- test/all_tests.rb
|
39
|
+
- test/benchmarks.rb
|
40
|
+
- test/db/dump.rdb
|
41
|
+
- test/db/redis.pid
|
42
|
+
- test/model_test.rb
|
43
|
+
- test/test.conf
|
44
|
+
- test/test_helper.rb
|
45
|
+
- test/validations_test.rb
|
46
|
+
has_rdoc: false
|
47
|
+
homepage: http://github.com/soveran/ohm
|
48
|
+
licenses: []
|
49
|
+
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.3.2
|
71
|
+
signing_key:
|
72
|
+
specification_version: 2
|
73
|
+
summary: Object-hash mapping library for Redis.
|
74
|
+
test_files: []
|
75
|
+
|