ohm 0.0.32 → 0.0.33
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/README.markdown +71 -36
- data/lib/ohm/collection.rb +186 -0
- data/lib/ohm/compat-1.8.6.rb +9 -0
- data/lib/ohm/key.rb +46 -0
- data/lib/ohm/redis.rb +0 -1
- data/lib/ohm.rb +238 -202
- data/test/indices_test.rb +27 -21
- data/test/model_test.rb +181 -45
- data/test/redis_test.rb +1 -1
- metadata +4 -2
data/README.markdown
CHANGED
@@ -3,7 +3,6 @@ Ohm ॐ
|
|
3
3
|
|
4
4
|
Object-hash mapping library for Redis.
|
5
5
|
|
6
|
-
|
7
6
|
Description
|
8
7
|
-----------
|
9
8
|
|
@@ -47,7 +46,6 @@ Now, in an irb session you can test the Redis adapter directly:
|
|
47
46
|
>> Ohm.redis.get "Foo"
|
48
47
|
=> "Bar"
|
49
48
|
|
50
|
-
|
51
49
|
Models
|
52
50
|
------
|
53
51
|
|
@@ -59,8 +57,8 @@ the example below:
|
|
59
57
|
|
60
58
|
class Event < Ohm::Model
|
61
59
|
attribute :name
|
62
|
-
|
63
|
-
|
60
|
+
reference :venue, Venue
|
61
|
+
set :participants, Person
|
64
62
|
counter :votes
|
65
63
|
|
66
64
|
index :name
|
@@ -70,9 +68,18 @@ the example below:
|
|
70
68
|
end
|
71
69
|
end
|
72
70
|
|
71
|
+
class Venue < Ohm::Model
|
72
|
+
attribute :name
|
73
|
+
collection :events, Event
|
74
|
+
end
|
75
|
+
|
76
|
+
class Person < Ohm::Model
|
77
|
+
attribute :name
|
78
|
+
end
|
79
|
+
|
73
80
|
All models have the `id` attribute built in, you don't need to declare it.
|
74
81
|
|
75
|
-
This is how you interact with
|
82
|
+
This is how you interact with IDs:
|
76
83
|
|
77
84
|
event = Event.create :name => "Ohm Worldwide Conference 2031"
|
78
85
|
event.id
|
@@ -92,13 +99,16 @@ validations. Keep reading to find out what you can do with models.
|
|
92
99
|
Attribute types
|
93
100
|
---------------
|
94
101
|
|
95
|
-
Ohm::Model provides four attribute types:
|
96
|
-
|
102
|
+
Ohm::Model provides four attribute types: {Ohm::Model::attribute
|
103
|
+
attribute}, {Ohm::Model::set set}, {Ohm::Model::list list}
|
104
|
+
and {Ohm::Model::counter counter}; and two meta types:
|
105
|
+
{Ohm::Model::reference reference} and {Ohm::Model::collection
|
106
|
+
collection}.
|
97
107
|
|
98
108
|
### attribute
|
99
109
|
|
100
110
|
An `attribute` is just any value that can be stored as a string. In the
|
101
|
-
example above, we used this field to store the
|
111
|
+
example above, we used this field to store the event's `name`. You can
|
102
112
|
use it to store numbers, but be aware that Redis will return a string
|
103
113
|
when you retrieve the value.
|
104
114
|
|
@@ -122,6 +132,17 @@ the value, but you can not assign it. In the example above, we used a
|
|
122
132
|
counter attribute for tracking votes. As the incr and decr operations
|
123
133
|
are atomic, you can rest assured a vote won't be counted twice.
|
124
134
|
|
135
|
+
### reference
|
136
|
+
|
137
|
+
It's a special kind of attribute that references another model.
|
138
|
+
Internally, Ohm will keep a pointer to the model (its ID), but you get
|
139
|
+
accessors that give you real instances. You can think of it as the model
|
140
|
+
containing the foreign key to another model.
|
141
|
+
|
142
|
+
### collection
|
143
|
+
|
144
|
+
Provides an accessor to search for all models that `reference` the current model.
|
145
|
+
|
125
146
|
Persistence strategy
|
126
147
|
--------------------
|
127
148
|
|
@@ -150,54 +171,69 @@ If you are saving the object, this will suffice:
|
|
150
171
|
event.comments << "Wonderful event!"
|
151
172
|
end
|
152
173
|
|
174
|
+
Working with Sets
|
175
|
+
-----------------
|
153
176
|
|
154
|
-
|
155
|
-
------------
|
156
|
-
|
157
|
-
Ohm lets you use collections (lists and sets) to represent associations.
|
158
|
-
For this, you only need to provide a second parameter when declaring a
|
159
|
-
list or a set:
|
160
|
-
|
161
|
-
set :attendees, Person
|
162
|
-
|
163
|
-
After this, every time you refer to `event.attendees` you will be talking
|
164
|
-
about instances of the model `Person`. If you want to get the raw values
|
165
|
-
of the set, you can use `event.attendees.raw`.
|
177
|
+
Given the following model declaration:
|
166
178
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
for {Ohm::Attributes::Collection#sort}.
|
179
|
+
class Event < Ohm::Model
|
180
|
+
attribute :name
|
181
|
+
set :attendees, Person
|
182
|
+
end
|
172
183
|
|
173
|
-
|
174
|
-
|
184
|
+
You can add instances of `Person` to the set of attendees with the
|
185
|
+
`<<` method:
|
175
186
|
|
176
|
-
@event.attendees
|
187
|
+
@event.attendees << Person.create(name: "Albert")
|
177
188
|
|
178
189
|
# And now...
|
179
190
|
@event.attendees.each do |person|
|
180
191
|
# ...do what you want with this person.
|
181
192
|
end
|
182
193
|
|
183
|
-
|
184
|
-
|
185
|
-
|
194
|
+
Sorting
|
195
|
+
-------
|
196
|
+
|
197
|
+
Since `attendees` is a {Ohm::Model::Set Set}, it exposes two sorting
|
198
|
+
methods: {Ohm::Model::Collection#sort sort} returns the elements
|
199
|
+
ordered by `id`, and {Ohm::Model::Collection#sort_by sort_by} receives
|
200
|
+
a parameter with an attribute name, which will determine the sorting
|
201
|
+
order. Both methods receive an options hash which is explained in the
|
202
|
+
documentation for {Ohm::Model::Collection#sort sort}.
|
203
|
+
|
204
|
+
Associations
|
205
|
+
------------
|
206
|
+
|
207
|
+
Ohm lets you declare `references` and `collections` to represent associations.
|
208
|
+
|
209
|
+
class Post < Ohm::Model
|
210
|
+
attribute :title
|
211
|
+
attribute :body
|
212
|
+
collection :comments, Comment
|
213
|
+
end
|
214
|
+
|
215
|
+
class Comment < Ohm::Model
|
216
|
+
attribute :body
|
217
|
+
reference :post, Post
|
218
|
+
end
|
186
219
|
|
220
|
+
After this, every time you refer to `post.comments` you will be talking
|
221
|
+
about instances of the model `Comment`. If you want to get a list of IDs
|
222
|
+
you can use `post.comments.raw`.
|
187
223
|
|
188
224
|
Indexes
|
189
225
|
-------
|
190
226
|
|
191
227
|
An index is a set that's handled automatically by Ohm. For any index declared,
|
192
|
-
Ohm maintains different sets of objects
|
228
|
+
Ohm maintains different sets of objects IDs for quick lookups.
|
193
229
|
|
194
|
-
|
195
|
-
allow for searches like Event.find(name: "some value")
|
230
|
+
In the `Event` example, the index on the name attribute will
|
231
|
+
allow for searches like `Event.find(name: "some value")`.
|
196
232
|
|
197
233
|
Note that the `assert_unique` validation and the methods `find` and `except` need a
|
198
234
|
corresponding index in order to work.
|
199
235
|
|
200
|
-
### Finding
|
236
|
+
### Finding records
|
201
237
|
|
202
238
|
You can find a collection of records with the `find` method:
|
203
239
|
|
@@ -230,7 +266,6 @@ are valid. Nesting assertions is a good practice, and you are also
|
|
230
266
|
encouraged to create your own assertions. You can trigger validations at
|
231
267
|
any point by calling `valid?` on a model instance.
|
232
268
|
|
233
|
-
|
234
269
|
Assertions
|
235
270
|
-----------
|
236
271
|
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module Ohm
|
2
|
+
class Collection
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_accessor :key, :db
|
6
|
+
|
7
|
+
def initialize(key, db = Ohm.redis)
|
8
|
+
self.key = key
|
9
|
+
self.db = db
|
10
|
+
end
|
11
|
+
|
12
|
+
def each(&block)
|
13
|
+
all.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Return the values as model instances, ordered by the options supplied.
|
17
|
+
# Check redis documentation to see what values you can provide to each option.
|
18
|
+
#
|
19
|
+
# @param options [Hash] options to sort the collection.
|
20
|
+
# @option options [#to_s] :by Model attribute to sort the instances by.
|
21
|
+
# @option options [#to_s] :order (ASC) Sorting order, which can be ASC or DESC.
|
22
|
+
# @option options [Integer] :limit (all) Number of items to return.
|
23
|
+
# @option options [Integer] :start (0) An offset from where the limit will be applied.
|
24
|
+
#
|
25
|
+
# @example Get the first ten users sorted alphabetically by name:
|
26
|
+
#
|
27
|
+
# @event.attendees.sort(:by => :name, :order => "ALPHA", :limit => 10)
|
28
|
+
#
|
29
|
+
# @example Get five posts sorted by number of votes and starting from the number 5 (zero based):
|
30
|
+
#
|
31
|
+
# @blog.posts.sort(:by => :votes, :start => 5, :limit => 10")
|
32
|
+
def sort(options = {})
|
33
|
+
return [] if empty?
|
34
|
+
options[:start] ||= 0
|
35
|
+
options[:limit] = [options[:start], options[:limit]] if options[:limit]
|
36
|
+
db.sort(key, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sort the model instances by id and return the first instance
|
40
|
+
# found. If a :by option is provided with a valid attribute name, the
|
41
|
+
# method sort_by is used instead and the option provided is passed as the
|
42
|
+
# first parameter.
|
43
|
+
#
|
44
|
+
# @see #sort
|
45
|
+
# @return [Ohm::Model, nil] Returns the first instance found or nil.
|
46
|
+
def first(options = {})
|
47
|
+
options = options.merge(:limit => 1)
|
48
|
+
sort(options).first
|
49
|
+
end
|
50
|
+
|
51
|
+
def [](index)
|
52
|
+
first(:start => index)
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_ary
|
56
|
+
all
|
57
|
+
end
|
58
|
+
|
59
|
+
def ==(other)
|
60
|
+
to_ary == other
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [true, false] Returns whether or not the collection is empty.
|
64
|
+
def empty?
|
65
|
+
size.zero?
|
66
|
+
end
|
67
|
+
|
68
|
+
# Clears the values in the collection.
|
69
|
+
def clear
|
70
|
+
db.del(key)
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Appends the given values to the collection.
|
75
|
+
def concat(values)
|
76
|
+
values.each { |value| self << value }
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Replaces the collection with the passed values.
|
81
|
+
def replace(values)
|
82
|
+
clear
|
83
|
+
concat(values)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Represents a Redis list.
|
88
|
+
#
|
89
|
+
# @example Use a list attribute.
|
90
|
+
#
|
91
|
+
# class Event < Ohm::Model
|
92
|
+
# attribute :name
|
93
|
+
# list :participants
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# event = Event.create :name => "Redis Meeting"
|
97
|
+
# event.participants << "Albert"
|
98
|
+
# event.participants << "Benoit"
|
99
|
+
# event.participants.all
|
100
|
+
# # => ["Albert", "Benoit"]
|
101
|
+
class List < Collection
|
102
|
+
|
103
|
+
# @param value [#to_s] Pushes value to the tail of the list.
|
104
|
+
def << value
|
105
|
+
db.rpush(key, value)
|
106
|
+
end
|
107
|
+
|
108
|
+
alias push <<
|
109
|
+
|
110
|
+
# @return [String] Return and remove the last element of the list.
|
111
|
+
def pop
|
112
|
+
db.rpop(key)
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [String] Return and remove the first element of the list.
|
116
|
+
def shift
|
117
|
+
db.lpop(key)
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param value [#to_s] Pushes value to the head of the list.
|
121
|
+
def unshift(value)
|
122
|
+
db.lpush(key, value)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [Array] Elements of the list.
|
126
|
+
def all
|
127
|
+
db.list(key)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @return [Integer] Returns the number of elements in the list.
|
131
|
+
def size
|
132
|
+
db.llen(key)
|
133
|
+
end
|
134
|
+
|
135
|
+
def include?(value)
|
136
|
+
all.include?(value)
|
137
|
+
end
|
138
|
+
|
139
|
+
def inspect
|
140
|
+
"#<List: #{all.inspect}>"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Represents a Redis set.
|
145
|
+
#
|
146
|
+
# @example Use a set attribute.
|
147
|
+
#
|
148
|
+
# class Company < Ohm::Model
|
149
|
+
# attribute :name
|
150
|
+
# set :employees
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# company = Company.create :name => "Redis Co."
|
154
|
+
# company.employees << "Albert"
|
155
|
+
# company.employees << "Benoit"
|
156
|
+
# company.employees.all #=> ["Albert", "Benoit"]
|
157
|
+
# company.employees.include?("Albert") #=> true
|
158
|
+
class Set < Collection
|
159
|
+
|
160
|
+
# @param value [#to_s] Adds value to the list.
|
161
|
+
def << value
|
162
|
+
db.sadd(key, value)
|
163
|
+
end
|
164
|
+
|
165
|
+
def delete(value)
|
166
|
+
db.srem(key, value)
|
167
|
+
end
|
168
|
+
|
169
|
+
def include?(value)
|
170
|
+
db.sismember(key, value)
|
171
|
+
end
|
172
|
+
|
173
|
+
def all
|
174
|
+
db.smembers(key)
|
175
|
+
end
|
176
|
+
|
177
|
+
# @return [Integer] Returns the number of elements in the set.
|
178
|
+
def size
|
179
|
+
db.scard(key)
|
180
|
+
end
|
181
|
+
|
182
|
+
def inspect
|
183
|
+
"#<Set: #{all.inspect}>"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/lib/ohm/compat-1.8.6.rb
CHANGED
data/lib/ohm/key.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Ohm
|
2
|
+
|
3
|
+
# Represents a key in Redis.
|
4
|
+
class Key
|
5
|
+
attr :parts
|
6
|
+
attr :glue
|
7
|
+
attr :namespace
|
8
|
+
|
9
|
+
def self.[](*parts)
|
10
|
+
Key.new(parts)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(parts, glue = ":", namespace = [])
|
14
|
+
@parts = parts
|
15
|
+
@glue = glue
|
16
|
+
@namespace = namespace
|
17
|
+
end
|
18
|
+
|
19
|
+
def append(*parts)
|
20
|
+
@parts += parts
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def eql?(other)
|
25
|
+
to_s == other.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
alias == eql?
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
(namespace + [@parts.join(glue)]).join(":")
|
32
|
+
end
|
33
|
+
|
34
|
+
alias inspect to_s
|
35
|
+
alias to_str to_s
|
36
|
+
|
37
|
+
def volatile
|
38
|
+
@namespace = [:~]
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def group(glue = self.glue)
|
43
|
+
Key.new([self], glue, namespace.slice!(0, namespace.size))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|