redisted 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea
6
+ .idea/*
7
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in redisted.gemspec
4
+ gemspec
5
+
6
+ group :development,:test do
7
+ gem 'rails', '3.1.0'
8
+ gem 'rspec'
9
+ gem 'rspec-rails'
10
+ gem 'capybara'
11
+ gem 'guard-rspec'
12
+ gem 'growl'
13
+ gem 'spork','> 0.9.0.rc'
14
+ gem 'guard-spork'
15
+ gem "pry"
16
+ end
data/Guardfile ADDED
@@ -0,0 +1,31 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
5
+ watch('config/application.rb')
6
+ watch('config/environment.rb')
7
+ watch(%r{^config/environments/.+\.rb$})
8
+ watch(%r{^config/initializers/.+\.rb$})
9
+ watch('spec/spec_helper.rb')
10
+ watch(%r{^spec/support/.+\.rb$})
11
+ end
12
+
13
+ guard 'rspec', :version => 2, :cli=>"--drb", :all_on_start=>false, :all_on_pass=>false do
14
+ watch(%r{^spec/.+_spec\.rb$})
15
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
16
+ watch('spec/spec_helper.rb') { "spec" }
17
+
18
+ # Rails example
19
+ watch(%r{^spec/.+_spec\.rb$})
20
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
21
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
22
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
23
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
24
+ watch('spec/spec_helper.rb') { "spec" }
25
+ watch('config/routes.rb') { "spec/routing" }
26
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
27
+ # Capybara request specs
28
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
29
+ end
30
+
31
+
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.