redisted 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/readme.md ADDED
@@ -0,0 +1,643 @@
1
+ PLEASE NOTE
2
+ ===========
3
+
4
+ This is still ***UNDER DEVELOPMENT*** and not yet ready for prime-time. Feel free to fool around with it, file bug
5
+ reports (via GitHub), and make suggestions. It is a work in progress. However, I do hope to have a fully
6
+ flushed out implementation as soon as possible.
7
+
8
+
9
+ Redisted
10
+ ========
11
+
12
+ Redisted provides higher level model functionality for redis databases. It is not intended as an ActiveRecord
13
+ plugin. If you want to use ActiveRecord, use a SQL database or something like Mongoid. Redisted is designed
14
+ to provide model symatics but customized and optimized for redis data store.
15
+
16
+ Do not expect ActiveRecord compatibility, but expect ActiveRecord-like capabilities highly taylored and customized to
17
+ take best advantage of Redis.
18
+
19
+ Installing
20
+ ==========
21
+
22
+ TODO
23
+
24
+ Configuring
25
+ ===========
26
+
27
+ TODO
28
+
29
+ Creating Models
30
+ ===============
31
+ A basic Redisted model looks like this:
32
+
33
+ class MyModel < Redisted::Base
34
+ end
35
+
36
+ Within the model, you specify fields, indices, relations, scopes, and additional model functionality, just like
37
+ you do with ActiveRecord models.
38
+
39
+ Instantiating Instances
40
+ =======================
41
+ Creating an instance is as simple as new or create:
42
+
43
+ # Creating an instance in memory only
44
+ obj=MyModel.new
45
+
46
+ # Creating and persisting an instance to Redis:
47
+ obj=MyModel.create
48
+ puts obj.id # <<<---An 'id' is created when it is persisted to Redis.
49
+
50
+ # Creating in memory, then later saving to Redis:
51
+ obj=MyModel.new
52
+ ...
53
+ obj.save
54
+ puts obj.id # <<<---The 'id' was created at time of 'save'
55
+
56
+ Fields
57
+ ======
58
+ Redis is a basic key/value store that is inheritantly schema-less. Redisted continues this by allowing the dynamic
59
+ specification fields without having to create a static schema that must be migrated. Simply adding a field
60
+ to the model specification makes it available. No database migration is necessary.
61
+
62
+ To specify a field in Redisted, you use the field command. This command looks like this:
63
+
64
+ field :field_name,type: :field_type
65
+
66
+ Field type can be one of :string, :integer, or :datetime. Here is an example class with fields defined:
67
+
68
+ class MyModel < Redisted::Base
69
+ field :name, type: :string
70
+ field :size, type: :integer
71
+ field :created, type: :datetime
72
+ end
73
+
74
+ Instances of a Redisted model are stored in a single Redis hash. The name of the key is based on the name of the
75
+ model and the ID associated with that model. For example, a MyModel instance (such as above), with an ID value
76
+ of 1234 would be stored in a hash under the key:
77
+
78
+ mymodel:1234
79
+
80
+ Individual fields within the hash would correspond to each field within the model. Each field is stored in Redis as
81
+ a string, and Redisted deals with converting that string to/from the desired field types, defined above.
82
+
83
+ Destroying an instance of a Redisted model is as simple as deleting the corresponding Redis key.
84
+
85
+ Reading/Writing Fields
86
+ ----------------------
87
+ Reading/writing a value to a field is as simple as using the included accessors:
88
+
89
+ class MyModel < Redisted::Base
90
+ field :name, type: :string
91
+ field :size, type: :integer
92
+ field :created, type: :datetime
93
+ end
94
+
95
+ obj=MyModel.create
96
+ obj.name="My Name"
97
+ puts "Name: #{obj.name}"
98
+
99
+ If the object is persisted to Redis (using 'create' to make the object or a previous call to 'save', and therefore it has an 'id'), then
100
+ by default reading/writing attributes read and write the value directly from Redis. For instance, using the above model:
101
+
102
+ obj=MyModel.create
103
+ obj.name="MyName" # Issues Redis call: HSET my_model[123] name "MyName"
104
+ puts "Name: #{obj.name}" # Issues Redis call: HGET my_model[123] name
105
+
106
+ To save round trip latency to Redis when you are making several changes to an instance, you can cache the changes:
107
+
108
+ obj=MyModel.create
109
+ obj.cache do
110
+ obj.name="MyName"
111
+ obj.size=123
112
+ obj.size=obj.size+222
113
+ end # Issues Redis call: HMSET my_model[112334] name "MyName" size "345"
114
+
115
+ Or:
116
+
117
+ obj=MyModel.create
118
+ obj.cache
119
+ obj.name="MyName"
120
+ obj.size=123
121
+ obj.size=obj.size+222
122
+ obj.save # Issues Redis call: HMSET my_model[112334] name "MyName" size "345"
123
+
124
+ You can also pass in a hash to the create call:
125
+
126
+ obj=MyModel.create({name: "MyName", size: 123}) # Issue Redis call:HMSETmy_model[112334] name "MyName" size "123"
127
+
128
+ When an object has not yet been persisted to disk (has no 'id'), this is the normal behavior:
129
+
130
+ obj=MyModel.new # <<<---Caching is on until the first save...
131
+ obj.name="MyName"
132
+ obj.size=123
133
+ obj.size=obj.size+222
134
+ obj.save # Issues Redis call: HMSET my_model[123] name "MyName" size "345"
135
+ obj.name="MyName2" # <<<---This now reverts to normal, non-caching functionality and issues immediately: HSET my_model[123] name "MyName2"
136
+
137
+ You can change the default persisting strategy for an entire class within the class definition:
138
+
139
+ class MyModel < Redisted::Base
140
+ always_cache_until_save # <<<--- Tells Redisted to always cache each change until 'save' is called
141
+ field :name, type: :string
142
+ field :size, type: :integer
143
+ field :created, type: :datetime
144
+ end
145
+ obj=MyModel.create
146
+ obj.cache
147
+ obj.name="MyName"
148
+ obj.size=123
149
+ obj.size=obj.size+222
150
+ obj.save # Issues Redis call: HMSET my_model[123] name "MyName" size "345"
151
+
152
+ For reading, the value is first checked to see if it has already been cached (by a previous read or write). If not, then
153
+ it will be read directly from Redis:
154
+
155
+ obj=MyModel.find(123) #<<<--- Assumes an object with this 'id' was previously created
156
+ puts obj.name # Issues Redis call: HGET my_model[123] name
157
+ puts obj.name # Reads from cache...no Redis call made
158
+ obj.name="MyName" # Issues redis call: HSET my_model[123] name "MyName"
159
+ puts obj.name # Reads from cache...no Redis call made
160
+
161
+ You can flush the cache and force a reread from Redis at any time:
162
+
163
+ obj=MyModel.find(123) #<<<--- Assumes an object with this 'id' was previously created
164
+ puts obj.name # Issues Redis call: HGET my_model[123] name
165
+ puts obj.name # Reads from cache...no Redis call made
166
+ obj.name="MyName" # Issues redis call: HSET my_model[123] name "MyName"
167
+ puts obj.name # Reads from cache...no Redis call made
168
+ obj.flush
169
+ puts obj.name # Issues Redis call: HGET my_model[123] name
170
+
171
+ Cache Pre-Read
172
+ --------------
173
+ You can force an object to always pre-read all values into the cache at object create by specifying an object in the
174
+ model class:
175
+
176
+ class MyModel < Redisted::Base
177
+ cache_on_first_get # <<<--- Tells Redisted to always cache when an object is instantiated from Redis
178
+ field :name, type: :string
179
+ field :size, type: :integer
180
+ field :created, type: :datetime
181
+ end
182
+ obj=MyModel.find(123) # Issues Redisc all: HMGET my_model[123] name size created
183
+ puts obj.name # Reads from cache...no Redis call made
184
+
185
+ You can specify which keys you want to pre-read in a couple different ways:
186
+
187
+ pre_cache_all # Pre-read all keys
188
+ pre_cache_all keys: :all # Same as above
189
+ pre_cache_all keys: [:name,:size] # Only pre-read name &size, and not created (in this example)
190
+ pre_cache_all except: [:content] # Only pre-read name &size, and not created (in this example)
191
+
192
+ You can specify when the pre-read occurs individually:
193
+
194
+ pre_cache_all when: :create # Load the cache when the object is first created (via find, or any other way)
195
+ pre_cache_all when: :first_read # Defer loading the cache until the first pre-cachable field is read from the object, then load the entire cache
196
+
197
+ List of Fields
198
+ --------------
199
+ You can get a list of available fields from either a class or an object as follows:
200
+
201
+ obj=MyModel.new
202
+ field_list=MyModel.fields
203
+ or:
204
+ field_list=obj.fields
205
+
206
+ This will return a hash with each key representing a field, and the value being a list of options provided during the
207
+ field create. For instance:
208
+
209
+ class MyModel < Redisted::Base
210
+ field :name, type: :string
211
+ field :size, type: :integer, default: 0
212
+ field :created, type: :datetime
213
+ end
214
+ field_list=MyModel.fields
215
+
216
+ This will produce the following hash:
217
+
218
+ {
219
+ name: {
220
+ type: :string
221
+ },
222
+ size: {
223
+ type: :integer,
224
+ default: 0
225
+ },
226
+ created: {
227
+ type: :datetime
228
+ },
229
+ }
230
+
231
+ Note that there may be other values in field-specific hash, such as default option values, etc., and the list may
232
+ change over time. You should only assume that values you specifically add to the field definition appear in this
233
+ hash, and that other values may or may not be present.
234
+
235
+ Basic Find
236
+ ==========
237
+
238
+ The easiest way to locate and open an object from Redis is to use find. Find expects one parameter, the 'id' of the object
239
+ you wish to open:
240
+
241
+ class MyModel < Redisted::Base
242
+ field :name, type: :string
243
+ field :size, type: :integer, default: 0
244
+ field :created, type: :datetime
245
+ end
246
+ obj=MyModel.find(1963)
247
+ puts obj.id # Returns 1963
248
+ puts obj.name # Returns the name of the object with 1963
249
+ ...
250
+
251
+ You can also pass an array of 'id' values, and find will return an array of objects:
252
+
253
+ class MyModel < Redisted::Base
254
+ field :name, type: :string
255
+ field :size, type: :integer, default: 0
256
+ field :created, type: :datetime
257
+ end
258
+ objs=MyModel.find([1963,1982,1994])
259
+ puts objs[0].id # Returns 1963
260
+ puts objs[0].name # Returns the name of object with id 1963
261
+ puts objs[1].id # Returns 1982
262
+ puts objs[1].name # Returns the name of object with id 1982
263
+ puts objs[2].id # Returns 1994
264
+ puts objs[2].name # Returns the name of object with id 1994
265
+
266
+ Delete and Destroy
267
+ ==================
268
+
269
+ You can delete an object by calling the delete method on an instance:
270
+
271
+ class MyModel < Redisted::Base
272
+ field :name, type: :string
273
+ field :size, type: :integer, default: 0
274
+ end
275
+ obj=MyModel.create({name: "test", size: 15}) # Redis call: HMSET my_model:1122 name "test" size "15"
276
+ obj.delete # Redis call: DEL my_model:1122
277
+
278
+ Destroy does the same thing, but you can specify standard callbacks to be run (see callbacks, below).
279
+
280
+ Indices
281
+ =======
282
+
283
+ Indices in Redisted are different than in other similar packages. Indices are *required* in order to filter, sort, or
284
+ determine uniqueness of items in a model. The entire filter/sort UI is dependent on the creation of indices.
285
+
286
+ Unique Index
287
+ ------------
288
+
289
+ A unique index is an index that specifies a field or group of fields who's value must be unique across all items
290
+ of the given model. It is specified as such:
291
+
292
+ class MyModel < Redisted::Base
293
+ field :name, type: :string
294
+ field :test_int, type: :integer
295
+
296
+ index :name, unique: true
297
+ end
298
+
299
+ In this example, all instances of MyModel must maintain a unique "name" field. This information is persisted in
300
+ Redis using a *SET*. The set is stored in a key specific to the model and the index. The redis key for the "name"
301
+ unique index in Redis for the model above may look like this:
302
+
303
+ SET: my_model[name]
304
+
305
+ Every time a new MyModel instance is persisted, a member is added to this SET. Everytime it is deleted, the corresponding
306
+ member is deleted. Each time the value of "name" changes, the old member is removed and a new one is added. All of this
307
+ occurs atomically using optimistic locking on this set key and the model hash key.
308
+
309
+ If you try and create/persiste an instance of the model where the "name"is not unique, an exception will be raised.
310
+ If you are performing saves one change at a time (the default), the check is made at the time when the assignment
311
+ is made to the name field. If you are using cached writes, it is performed at the time of the save.
312
+
313
+ The above syntax creates a unique index named "name" that is unique across the single field "name". You can specify
314
+ unique indices in other ways as well:
315
+
316
+ index :ti, unique: :test_int
317
+ # Creates an index named "ti" that is unique across the field "test_int"
318
+ index :idxkey, unique: [:test_int,:name]
319
+ # Creates an index named "idxkey" that is unique across "test_int"/"name" field pair (the pair of values must be
320
+ # unique).
321
+
322
+ The index name must be unique across all indexes for a given model.
323
+
324
+
325
+ Filter/Sort Index
326
+ -----------------
327
+ A filter/sort index is an index that can be used to return a filtered set of items and/or sorted in a particular way.
328
+
329
+ Here is a simple example:
330
+
331
+ class MyModel < Redisted::Base
332
+ field :name, type: :string
333
+ field :test_int, type: :integer
334
+
335
+ index :odd, includes: ->(elem){(elem.test_int%2)!=0}
336
+ index :even, includes: ->(elem){(elem.test_int%2)==0}
337
+ end
338
+
339
+ This model creates two indexes, one that contains a list of all model instances where test_int is odd, the other
340
+ contains a list of all model instances where test_int is even.
341
+
342
+ With the above specification, you can then use the following commands:
343
+
344
+ MyModel.by_odd.all # Returns an array of all instances of MyModel where test_int is odd...
345
+ MyModel.by_even.all # Returns an array of all instances of MyModel where test_int is even...
346
+
347
+ The order of the returned results is not specified. Using this example, the indexes are maintained in SORTED
348
+ SETS (ZADD/ZREM/etc.). The name of the keys are:
349
+
350
+ my_model[odd]
351
+ my_model[even]
352
+
353
+ Each key will contain a member referring to the 'id' value of the instance that is contained in this index. For
354
+ this example, the sort "score" is set to 1 (more on this later). Each time an instance is created or modified, the
355
+ lambda function for the index specified in the model declation is executed. If the function returns true, then an
356
+ entry for this instance is added to the index. If it is false, any existing entry for this instance is removed. When
357
+ an instance is deleted, it's entry (if it exists) is deleted.
358
+
359
+ You may specify a sort index as follows:
360
+
361
+ class MyModel < Redisted::Base
362
+ field :name, type: :string
363
+ field :test_int, type: :integer
364
+
365
+ index :asc, order: ->(elem){elem.test_int},fields: :test_int
366
+ end
367
+
368
+ This creates an index named asc, that is implemented as a SORTED SET with the following key:
369
+
370
+ my_model[asc]
371
+
372
+ In this case, *all* instances of MyModel have their 'id' value inserted as a member in this set, and their sort
373
+ "weight" is set to the value returned by the lambda function (order:).
374
+
375
+ With the above specification, when you use the index like so:
376
+
377
+ MyModel.by_asc.all
378
+
379
+ You will get an array of all objects in the model, sorted in ascending order by "test_int".
380
+
381
+ Note that the lambda function can perform any calculation and return any result. For a reverse sort, you can simply
382
+ put a minus sign in front of the equation.
383
+
384
+ While the lambda can contain any information, the value of the Redis "weight" (as returned by the method) is only
385
+ recalculated if one of the fields specified in the fields: parameter (which may be an array of fields) has changed
386
+ values. Thus, it's important to include in this array all fields that are used for the order calculation.
387
+
388
+ An index may be both a filter and a sort index, by combining the syntax:
389
+
390
+ index :sortedodd,includes: ->(elem){(elem.test_int%2)!=0}, order: ->(elem){elem.test_int}, fields: :test_int
391
+
392
+ In this case, the entry will only be included in the index if the "includes" lambda returns true, and when inserted
393
+ it will be given a sort "weight" of the value returned by the "order" lambda. The "weight"is only recalculated if
394
+ the "test_int" field changes value.
395
+
396
+ When using indexes, you can mix and match them in a single call. For instance, using all of the above indexes, you can
397
+ perform the following lookups:
398
+
399
+ MyModel.by_odd.all
400
+ # Returns an array of all instances of MyModel where test_int is odd...
401
+ MyModel.by_asc.all
402
+ # Returns all instances of MyModel sorted by test_int.
403
+ MyModel.by_odd.by_asc.all
404
+ # Returns all instances of MyModel where test_int is odd, sorted by test_int.
405
+ MyModel.by_sortedodd.all
406
+ # Same as above: Returns all instances of MyModel where test_int is odd, sorted by test_int.
407
+
408
+ Match Index
409
+ -----------
410
+
411
+ The above indexes provide a very high performant way of doing static filters. It works well when the specific
412
+ queries are known in advance. This is because the indexes contain a specific set of static keys, and membership in the
413
+ index is determined at the time the model is saved.
414
+
415
+ When possible, these indexes are efficient and powerful. However, it's not always possible to do a query this way.
416
+
417
+ That is why there are match indexes. A match index is used when a value needed for the query is not known ahead
418
+ of time (at model save time). Here is the declaration of a match index:
419
+
420
+ class MyModel < Redisted::Base
421
+ field :name, type: :string
422
+ field :test_int, type: :string
423
+ field :provider, type: :string
424
+
425
+ index :provider, match: ->(elem){(elem.provider)}
426
+ end
427
+
428
+ With an index like this, you can perform queries such as the following:
429
+
430
+ a=MyModel.by_provider("cable").all
431
+ or:
432
+ a=MyModel.by_provider("satellite").all
433
+
434
+ At model creation/save time, several SORTED SETS are created for this index, one for each unique value returned
435
+ by the various objects when they call the "match" lambda. For instance, in the above example, if the only values
436
+ that "provider" is ever set to are "cable", "satellite", or "overair", then the following keys are generated:
437
+
438
+ my_model[provider]:cable
439
+ my_model[provider]:satellite
440
+ my_model[provider]:overair
441
+
442
+ Each key is a SORTED SET who's members are the 'id' values of objects where the "match" lambda returns the specified
443
+ value. So, when you call 'by_provider("cable")', you include the 'my_model[provider]:cable' key in the query.
444
+
445
+ The individual keys are created the first time a value is returned by the "match" function (i.e., when the first
446
+ member is inserted). If a query is run for a value that does not yet have a key, such as the following:
447
+
448
+ MyModel.by_provider("xyzzy").all
449
+
450
+ then the query will act as if it returns an empty set (which it is).
451
+
452
+
453
+
454
+ Incomplete Queries
455
+ ------------------
456
+ Just like in ActiveRecord, you can store intermediate results of a nested query, and use the results later. For
457
+ instance, each of these does the exact same thing:
458
+
459
+ In line:
460
+
461
+ MyModel.by_odd.by_asc.all
462
+
463
+ Stored in a variable each step:
464
+
465
+ a=MyModel.by_odd
466
+ a=a.by_asc
467
+ a.all
468
+
469
+ Storing the query and processing it later:
470
+
471
+ a=MyModel.by_odd.by_asc
472
+ ...
473
+ a.all
474
+
475
+ Starting with a blank query:
476
+
477
+ a=MyModel.scoped
478
+ a=a.by_odd
479
+ a=a.by_asc
480
+ a.all
481
+
482
+ The query is performed by calling ZINTERSTORE on each of the sorted sets. However, these ZINTERSTORE calls are not
483
+ performed until the ".all" is executed. This way you can setup a query in a controller, for instance, and it only
484
+ will execute if the view issues the ".all" on it.
485
+
486
+ Besides ".all", you can also use ".each":
487
+
488
+ MyModel.by_odd.by_asc.each do |model|
489
+ # 'model' contains each instance that matches the query
490
+ end
491
+
492
+
493
+ Updating An Index
494
+ -----------------
495
+ Given how indexes are created and handled, it is recommended that you do *not* modify the lambda function of an
496
+ index after the index has been created and populated. Doing so is inviting problems, since the index is not
497
+ regenerated when the function changes.
498
+
499
+ If you must change the meaning of an index, we recommend you actually create a new index and begin using that, then
500
+ obsolete the old one when it is not used any more. Using scopes (discussed below) rather than raw index can help
501
+ make this easier.
502
+
503
+ If you *must* change the lambda function, then you should rebuild the indexes for the model from scratch immediately
504
+ after changing the function.
505
+
506
+ From the command line:
507
+
508
+ rake redisted:recalculate_all
509
+
510
+ Programatically (one model):
511
+
512
+ MyModel.recalculate
513
+
514
+ Programatically (all models):
515
+
516
+ Redisted::recalculate_all
517
+
518
+ For each index, this will cause the old index to be destroyed, and a new one to be regenerated. Depending on the
519
+ number and complexity of the indexes, and the number of objects in the model, this could take some time. Since
520
+ indexes in redisted are *required* to do queries, rather than simply providing performance enhancements, access
521
+ to the query functionality of the model is offline while this reindex occurs, and hence should be performed during
522
+ a maintenance window.
523
+
524
+ TODO: Need a way to specify to *pause* query calls rather than failing them during an index recalcuate.
525
+
526
+ TODO: Note that this entire recalculate interfact does not yet exist.
527
+
528
+ Index Uniqueness
529
+ ----------------
530
+
531
+ The names of the indexes must be unique across all indexes for this model. This goes for unique, filter/sort,
532
+ and 'match' indexes.
533
+
534
+ Scopes
535
+ ======
536
+
537
+ Using scopes, you can specify a complex query and refer to it using a simple identifer. For example, combining
538
+ much of the above:
539
+
540
+ class MyModel < Redisted::Base
541
+ field :name, type: :string
542
+ field :test_int, type: :integer
543
+ field :provider, type: :string
544
+
545
+ index :odd, includes: ->(elem){(elem.test_int%2)!=0}
546
+ index :even, includes: ->(elem){(elem.test_int%2)==0}
547
+ index :provider, match: ->(elem){(elem.provider)}
548
+ index :asc, order: ->(elem){elem.test_int},fields: :test_int
549
+
550
+ scope :sorted_odd, ->{by_odd.by_asc}
551
+ scope :odd_providers, ->(name){by_odd.provider(name)}
552
+ end
553
+
554
+ Then, you can use them in queries:
555
+
556
+ MyModel.sorted_odd.all
557
+ MyModel.odd_providers.all
558
+
559
+ Validations
560
+ ===========
561
+
562
+ Standard ActiveModel validations work with Redisted. Such as:
563
+
564
+ class MyModel < Redisted::Base
565
+ field :name, type: :string
566
+ field :test_int, type: :integer
567
+ field :provider, type: :string
568
+
569
+ validates_length_of :name, :minumum=>5
570
+ validates_length_of :provider, :maximum=>20
571
+ end
572
+
573
+ TODO:Note that "validates_uniqueness_of" is not yet supported...
574
+
575
+ References (aka Relationships)
576
+ ==============================
577
+ Redisted is great for creating models that are "in between" other non-Redisted models, such as ActiveRecord models or
578
+ Mongoid models. Redis in general is great for creating integrated maps of values, such as object references. In fact,
579
+ Redisted was created out of a need for a very fast way of assocating large number of 'tags' to a large number of
580
+ 'messages' stored in Mongoid very efficiently.
581
+
582
+ As such, the relation mechanims in Redisted are optimized for creating not only references to other Redisted models,
583
+ but to models of other backend stores, such as ActiveRecord and Mongoid.
584
+
585
+ There are only two reference types, references_one and references_many. Here is an example:
586
+
587
+ class MyModel < Redisted::Base
588
+ field :name, type: :string
589
+ field :test_int, type: :integer
590
+ field :provider, type: :string
591
+
592
+ references_one :user
593
+ references_many :message
594
+
595
+ references_one :another_class, as: :aclass
596
+ references_many :another_class, as: :theclasses
597
+ end
598
+
599
+ The "references_one" creates a reference to a single object of another model. The model does not have to be
600
+ a redisted model (but it can be), it can be ActiveRecord, Mongoid, anything that accepts a ".find(id)" call.
601
+
602
+ With a references_one field, you can:
603
+
604
+ u=ARUser.create
605
+ mm=MyModel.create
606
+
607
+ mm.user=u
608
+ or:
609
+ mm.user_id=u.id
610
+
611
+ u=mm.user # Returns an instance of ARUser
612
+ u=mm.user.xxx # Call method 'xxx' on the instance of ARUser
613
+
614
+ With a refererences_many field, you can:
615
+
616
+ m1=ARMessage.create
617
+ m2=ARMessage.create
618
+ mm=MyModel.create
619
+
620
+ mm.message << m1
621
+ mm.message << m2
622
+
623
+ mm.message[0] # Returns an instance of ARMessage (m1)
624
+ mm.message[1] # Returns an instance of ARMessage (m2)
625
+
626
+ This all will work as is, without any changes to the ARUser model. However, you will probably want to install
627
+ a destroy callback in ARUser so that the reference is removed if 'u' is destroyed. You can do that using
628
+ the following code:
629
+
630
+ class ARUser
631
+ Redisted::on_destroy :my_model,:user
632
+ end
633
+ class ARMessage
634
+ Redisted::on_destroy :my_model,:message
635
+
636
+
637
+ Callbacks
638
+ =========
639
+
640
+ Callbacks work the same as ActiveRecord. Redisted supports callbacks on create, update, save, and destroy.
641
+ For update and save, the callbacks are called anytime a value is written to Redisted. So, if the model is setup
642
+ so that each field update forces a write to Redisted (the default), then these two callbacks are called on each
643
+ field update.
data/redisted.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "redisted/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "redisted"
7
+ s.version = Redisted::VERSION
8
+ s.authors = ["Lee Atchison"]
9
+ s.email = ["lee@leeatchison.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Rails models based on Redis.}
12
+ s.description = %q{Rails models based Redis.}
13
+
14
+ s.rubyforge_project = "redisted"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_runtime_dependency "redis"
23
+ end