ohm 0.0.32 → 0.0.33

Sign up to get free protection for your applications and to get access to all the features.
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
- set :participants
63
- list :comments
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 ids:
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: `attribute`, `set`, `list` and
96
- `counter`.
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 Event's `name`. You can
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
- Associations
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
- The `attendees` collection also exposes two sorting methods: `sort`
168
- returns the elements ordered by `id`, and `sort_by` receives a parameter
169
- with an attribute name, which will determine the sorting order. Both
170
- methods receive an options hash which is explained in the documentation
171
- for {Ohm::Attributes::Collection#sort}.
179
+ class Event < Ohm::Model
180
+ attribute :name
181
+ set :attendees, Person
182
+ end
172
183
 
173
- Adding instances of `Person` to the attendees hash is done with the
174
- `add` method:
184
+ You can add instances of `Person` to the set of attendees with the
185
+ `<<` method:
175
186
 
176
- @event.attendees.add(Person.create(name: "Albert"))
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
- Just to clarify: when you add items to a set with `<<`, Ohm inserts
184
- whatever you send without checking it. When you use `add`, it assumes
185
- it's an instance of some `Ohm::Model` and stores its id.
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 ids for quick lookups.
228
+ Ohm maintains different sets of objects IDs for quick lookups.
193
229
 
194
- For example, in the example above, the index on the name attribute will
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
@@ -13,3 +13,12 @@ unless "".respond_to?(:lines)
13
13
  end
14
14
  end
15
15
  end
16
+
17
+ unless Object.new.respond_to?(:tap)
18
+ class Object
19
+ def tap
20
+ yield(self)
21
+ self
22
+ end
23
+ end
24
+ end
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
data/lib/ohm/redis.rb CHANGED
@@ -33,7 +33,6 @@ module Ohm
33
33
  :lpush => true,
34
34
  :lrem => true,
35
35
  :lset => true,
36
- :rpoplpush => true,
37
36
  :rpush => true,
38
37
  :sadd => true,
39
38
  :set => true,