redis_orm 0.4.2 → 0.5
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/CHANGELOG +4 -0
- data/Manifest +2 -0
- data/README.md +32 -3
- data/Rakefile +2 -2
- data/lib/redis_orm.rb +3 -0
- data/lib/redis_orm/associations/has_many.rb +4 -0
- data/lib/redis_orm/associations/has_many_helper.rb +25 -0
- data/lib/redis_orm/associations/has_many_proxy.rb +54 -6
- data/lib/redis_orm/redis_orm.rb +80 -15
- data/redis_orm.gemspec +7 -7
- data/test/association_indices_test.rb +145 -0
- data/test/associations_test.rb +6 -9
- data/test/dynamic_finders_test.rb +1 -1
- metadata +16 -12
data/CHANGELOG
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
v0.5 [02-07-2011]
|
2
|
+
* added support of *:conditions* hash in *:options* hash for has_many association in #find/#all methods
|
3
|
+
* made keys order-independent in *:conditions* hash
|
4
|
+
|
1
5
|
v0.4.2 [25-06-2011]
|
2
6
|
* fixed bug with wrong saving of :default value/index for boolean type, fixed bug with #find(:all), #find(:first), #find(:last) function calls, added test for it
|
3
7
|
* added simple test to ensure correct search on boolean properties
|
data/Manifest
CHANGED
@@ -7,10 +7,12 @@ lib/redis_orm.rb
|
|
7
7
|
lib/redis_orm/active_model_behavior.rb
|
8
8
|
lib/redis_orm/associations/belongs_to.rb
|
9
9
|
lib/redis_orm/associations/has_many.rb
|
10
|
+
lib/redis_orm/associations/has_many_helper.rb
|
10
11
|
lib/redis_orm/associations/has_many_proxy.rb
|
11
12
|
lib/redis_orm/associations/has_one.rb
|
12
13
|
lib/redis_orm/redis_orm.rb
|
13
14
|
redis_orm.gemspec
|
15
|
+
test/association_indices_test.rb
|
14
16
|
test/associations_test.rb
|
15
17
|
test/atomicity_test.rb
|
16
18
|
test/basic_functionality_test.rb
|
data/README.md
CHANGED
@@ -79,7 +79,7 @@ Dynamic finders work mostly the way they do in ActiveRecord. The only difference
|
|
79
79
|
|
80
80
|
## Options for #find/#all
|
81
81
|
|
82
|
-
To extract all or part of the associated records you could use
|
82
|
+
To extract all or part of the associated records you could use 4 options for now:
|
83
83
|
|
84
84
|
* :limit
|
85
85
|
|
@@ -98,14 +98,16 @@ To extract all or part of the associated records you could use 3 options for now
|
|
98
98
|
@album.photos << @photo2
|
99
99
|
@album.photos << @photo1
|
100
100
|
|
101
|
-
@album.photos.all(:limit => 0, :offset => 0)
|
102
|
-
@album.photos.all(:limit => 1, :offset => 0).size
|
101
|
+
@album.photos.all(:limit => 0, :offset => 0) # => []
|
102
|
+
@album.photos.all(:limit => 1, :offset => 0).size # => 1
|
103
103
|
@album.photos.all(:limit => 2, :offset => 0)
|
104
|
+
@album.photos.all(:limit => 1, :offset => 1, :conditions => {:image_type => "image/png"})
|
104
105
|
@album.photos.find(:all, :order => "asc")
|
105
106
|
|
106
107
|
Photo.find(:first, :order => "desc")
|
107
108
|
Photo.all(:order => "asc", :limit => 5)
|
108
109
|
Photo.all(:order => "desc", :limit => 10, :offset => 50)
|
110
|
+
Photo.all(:order => "desc", :offset => 10, :conditions => {:image_type => "image/jpeg"})
|
109
111
|
|
110
112
|
Photo.find(:all, :conditions => {:image => "facepalm.jpg"}) # => [...]
|
111
113
|
Photo.find(:first, :conditions => {:image => "boobs.png"}) # => @photo1
|
@@ -141,6 +143,33 @@ User.find_by_first_name_and_last_name("Robert", "Pirsig") # => user
|
|
141
143
|
User.find_all_by_first_name_and_last_name("Chris", "Pirsig") # => []
|
142
144
|
```
|
143
145
|
|
146
|
+
Indices on associations are also created/deleted/updated when objects with has_many/belongs_to associations are created/deleted/updated (excerpt from association_indices_test.rb):
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class Article < RedisOrm::Base
|
150
|
+
property :title, String
|
151
|
+
has_many :comments
|
152
|
+
end
|
153
|
+
|
154
|
+
class Comment < RedisOrm::Base
|
155
|
+
property :body, String
|
156
|
+
property :moderated, RedisOrm::Boolean, :default => false
|
157
|
+
index :moderated
|
158
|
+
belongs_to :article
|
159
|
+
end
|
160
|
+
|
161
|
+
article = Article.create :title => "DHH drops OpenID on 37signals"
|
162
|
+
comment1 = Comment.create :body => "test"
|
163
|
+
comment2 = Comment.create :body => "test #2", :moderated => true
|
164
|
+
|
165
|
+
article.comments << [comment1, comment2]
|
166
|
+
|
167
|
+
# here besides usual indices for each comment, 2 association indices are created so #find with *:conditions* on comments just works
|
168
|
+
|
169
|
+
article.comments.find(:all, :conditions => {:moderated => true})
|
170
|
+
article.comments.find(:all, :conditions => {:moderated => false})
|
171
|
+
```
|
172
|
+
|
144
173
|
Index definition supports following options:
|
145
174
|
|
146
175
|
* **:unique** Boolean default: false
|
data/Rakefile
CHANGED
@@ -4,8 +4,8 @@ require 'rake'
|
|
4
4
|
#=begin
|
5
5
|
require 'echoe'
|
6
6
|
|
7
|
-
Echoe.new('redis_orm', '0.
|
8
|
-
p.description = "ORM for Redis advanced key-value storage"
|
7
|
+
Echoe.new('redis_orm', '0.5') do |p|
|
8
|
+
p.description = "ORM for Redis (advanced key-value storage) with ActiveRecord API"
|
9
9
|
p.url = "https://github.com/german/redis_orm"
|
10
10
|
p.author = "Dmitrii Samoilov"
|
11
11
|
p.email = "germaninthetown@gmail.com"
|
data/lib/redis_orm.rb
CHANGED
@@ -5,6 +5,9 @@ require 'redis'
|
|
5
5
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'redis_orm', 'active_model_behavior')
|
6
6
|
|
7
7
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'redis_orm', 'associations', 'belongs_to')
|
8
|
+
|
9
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'redis_orm', 'associations', 'has_many_helper')
|
10
|
+
|
8
11
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'redis_orm', 'associations', 'has_many_proxy')
|
9
12
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'redis_orm', 'associations', 'has_many')
|
10
13
|
require File.join(File.dirname(File.expand_path(__FILE__)), 'redis_orm', 'associations', 'has_one')
|
@@ -47,6 +47,10 @@ module RedisOrm
|
|
47
47
|
# we use here *foreign_models_name* not *record.model_name.pluralize* because of the :as option
|
48
48
|
$redis.zadd("#{model_name}:#{id}:#{foreign_models_name}", Time.now.to_f, record.id)
|
49
49
|
|
50
|
+
record.get_indices.each do |index|
|
51
|
+
save_index_for_associated_record(index, record, [model_name, id, record.model_name.pluralize]) # record.model_name.pluralize => foreign_models_name
|
52
|
+
end
|
53
|
+
|
50
54
|
if !options[:as]
|
51
55
|
# article.comments = [comment1, comment2]
|
52
56
|
# iterate through the array of comments and create backlink
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RedisOrm
|
2
|
+
module Associations
|
3
|
+
module HasManyHelper
|
4
|
+
private
|
5
|
+
def save_index_for_associated_record(index, record, inception)
|
6
|
+
prepared_index = if index[:name].is_a?(Array) # TODO sort alphabetically
|
7
|
+
index[:name].inject(inception) do |sum, index_part|
|
8
|
+
sum += [index_part, record.send(index_part.to_sym)]
|
9
|
+
end.join(':')
|
10
|
+
else
|
11
|
+
inception += [index[:name], record.send(index[:name].to_sym)]
|
12
|
+
inception.join(':')
|
13
|
+
end
|
14
|
+
|
15
|
+
prepared_index.downcase! if index[:options][:case_insensitive]
|
16
|
+
|
17
|
+
if index[:options][:unique]
|
18
|
+
$redis.set(prepared_index, record.id)
|
19
|
+
else
|
20
|
+
$redis.zadd(prepared_index, Time.now.to_f, record.id)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module RedisOrm
|
2
2
|
module Associations
|
3
3
|
class HasManyProxy
|
4
|
+
include HasManyHelper
|
5
|
+
|
4
6
|
def initialize(reciever_model_name, reciever_id, foreign_models, options)
|
5
7
|
@records = [] #records.to_a
|
6
8
|
@reciever_model_name = reciever_model_name
|
@@ -31,6 +33,10 @@ module RedisOrm
|
|
31
33
|
new_records.to_a.each do |record|
|
32
34
|
$redis.zadd(__key__, Time.now.to_f, record.id)
|
33
35
|
|
36
|
+
record.get_indices.each do |index|
|
37
|
+
save_index_for_associated_record(index, record, [@reciever_model_name, @reciever_id, record.model_name.pluralize]) # record.model_name.pluralize => @foreign_models
|
38
|
+
end
|
39
|
+
|
34
40
|
if !@options[:as]
|
35
41
|
record_associations = record.get_associations
|
36
42
|
|
@@ -66,20 +72,42 @@ module RedisOrm
|
|
66
72
|
end
|
67
73
|
|
68
74
|
def all(options = {})
|
69
|
-
if options.is_a?(Hash) && (options[:limit] || options[:offset] || options[:order])
|
75
|
+
if options.is_a?(Hash) && (options[:limit] || options[:offset] || options[:order] || options[:conditions])
|
70
76
|
limit = if options[:limit] && options[:offset]
|
71
77
|
[options[:offset].to_i, options[:limit].to_i]
|
72
78
|
elsif options[:limit]
|
73
79
|
[0, options[:limit].to_i]
|
74
80
|
end
|
75
81
|
|
76
|
-
|
77
|
-
|
82
|
+
prepared_index = if options[:conditions] && options[:conditions].is_a?(Hash)
|
83
|
+
properties = options[:conditions].collect{|key, value| key}
|
84
|
+
|
85
|
+
index = @foreign_models.to_s.singularize.camelize.constantize.find_index(properties)
|
86
|
+
|
87
|
+
raise NotIndexFound if !index
|
88
|
+
|
89
|
+
construct_prepared_index(index, options[:conditions])
|
78
90
|
else
|
79
|
-
|
91
|
+
__key__
|
92
|
+
end
|
93
|
+
|
94
|
+
@records = []
|
95
|
+
|
96
|
+
# to DRY things up I use here check for index but *else* branch also imply that the index might have be used
|
97
|
+
# since *prepared_index* vary whether options[:conditions] are present or not
|
98
|
+
if index && index[:options][:unique]
|
99
|
+
id = $redis.get prepared_index
|
100
|
+
@records << @foreign_models.to_s.singularize.camelize.constantize.find(id)
|
101
|
+
else
|
102
|
+
ids = if options[:order].to_s == 'desc'
|
103
|
+
$redis.zrevrangebyscore(prepared_index, Time.now.to_f, 0, :limit => limit)
|
104
|
+
else
|
105
|
+
$redis.zrangebyscore(prepared_index, 0, Time.now.to_f, :limit => limit)
|
106
|
+
end
|
107
|
+
@records += @foreign_models.to_s.singularize.camelize.constantize.find(ids)
|
80
108
|
end
|
81
109
|
@fetched = true
|
82
|
-
@records
|
110
|
+
@records
|
83
111
|
else
|
84
112
|
fetch if !@fetched
|
85
113
|
@records
|
@@ -100,7 +128,7 @@ module RedisOrm
|
|
100
128
|
elsif token == :first
|
101
129
|
all(options.merge({:limit => 1}))[0]
|
102
130
|
elsif token == :last
|
103
|
-
reversed = options[:order] == '
|
131
|
+
reversed = options[:order] == 'desc' ? 'asc' : 'desc'
|
104
132
|
all(options.merge({:limit => 1, :order => reversed}))[0]
|
105
133
|
end
|
106
134
|
end
|
@@ -124,6 +152,26 @@ module RedisOrm
|
|
124
152
|
def __key__
|
125
153
|
@options[:as] ? "#{@reciever_model_name}:#{@reciever_id}:#{@options[:as]}" : "#{@reciever_model_name}:#{@reciever_id}:#{@foreign_models}"
|
126
154
|
end
|
155
|
+
|
156
|
+
# "article:1:comments:moderated:true"
|
157
|
+
def construct_prepared_index(index, conditions_hash)
|
158
|
+
prepared_index = [@reciever_model_name, @reciever_id, @foreign_models].join(':')
|
159
|
+
|
160
|
+
# in order not to depend on order of keys in *:conditions* hash we rather interate over the index itself and find corresponding values in *:conditions* hash
|
161
|
+
if index[:name].is_a?(Array)
|
162
|
+
index[:name].each do |key|
|
163
|
+
# raise if User.find_by_firstname_and_castname => there's no *castname* in User's properties
|
164
|
+
#raise ArgumentsMismatch if !@@properties[model_name].detect{|p| p[:name] == key.to_sym} # TODO
|
165
|
+
prepared_index += ":#{key}:#{conditions_hash[key]}"
|
166
|
+
end
|
167
|
+
else
|
168
|
+
prepared_index += ":#{index[:name]}:#{conditions_hash[index[:name]]}"
|
169
|
+
end
|
170
|
+
|
171
|
+
prepared_index.downcase! if index[:options][:case_insensitive]
|
172
|
+
|
173
|
+
prepared_index
|
174
|
+
end
|
127
175
|
end
|
128
176
|
end
|
129
177
|
end
|
data/lib/redis_orm/redis_orm.rb
CHANGED
@@ -29,7 +29,9 @@ module RedisOrm
|
|
29
29
|
class Base
|
30
30
|
include ActiveModel::Validations
|
31
31
|
include ActiveModelBehavior
|
32
|
-
|
32
|
+
|
33
|
+
include Associations::HasManyHelper
|
34
|
+
|
33
35
|
extend Associations::BelongsTo
|
34
36
|
extend Associations::HasMany
|
35
37
|
extend Associations::HasOne
|
@@ -51,7 +53,7 @@ module RedisOrm
|
|
51
53
|
|
52
54
|
# *options* currently supports
|
53
55
|
# *unique* Boolean
|
54
|
-
# *case_insensitive* Boolean
|
56
|
+
# *case_insensitive* Boolean
|
55
57
|
def index(name, options = {})
|
56
58
|
@@indices[model_name] << {:name => name, :options => options}
|
57
59
|
end
|
@@ -129,23 +131,31 @@ module RedisOrm
|
|
129
131
|
end
|
130
132
|
|
131
133
|
def find_index(properties)
|
134
|
+
properties.map!{|p| p.to_sym}
|
135
|
+
|
132
136
|
@@indices[model_name].detect do |models_index|
|
133
137
|
if models_index[:name].is_a?(Array) && models_index[:name].size == properties.size
|
134
|
-
|
138
|
+
# check the elements not taking into account their order
|
139
|
+
(models_index[:name] & properties).size == properties.size
|
135
140
|
elsif !models_index[:name].is_a?(Array) && properties.size == 1
|
136
|
-
models_index[:name] == properties[0]
|
141
|
+
models_index[:name] == properties[0]
|
137
142
|
end
|
138
143
|
end
|
139
144
|
end
|
140
145
|
|
141
|
-
def construct_prepared_index(index,
|
146
|
+
def construct_prepared_index(index, conditions_hash)
|
142
147
|
prepared_index = model_name.to_s
|
143
148
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
+
# in order not to depend on order of keys in *:conditions* hash we rather interate over the index itself and find corresponding values in *:conditions* hash
|
150
|
+
if index[:name].is_a?(Array)
|
151
|
+
index[:name].each do |key|
|
152
|
+
# raise if User.find_by_firstname_and_castname => there's no *castname* in User's properties
|
153
|
+
raise ArgumentsMismatch if !@@properties[model_name].detect{|p| p[:name] == key.to_sym}
|
154
|
+
prepared_index += ":#{key}:#{conditions_hash[key]}"
|
155
|
+
end
|
156
|
+
else
|
157
|
+
prepared_index += ":#{index[:name]}:#{conditions_hash[index[:name]]}"
|
158
|
+
end
|
149
159
|
|
150
160
|
prepared_index.downcase! if index[:options][:case_insensitive]
|
151
161
|
|
@@ -304,6 +314,11 @@ module RedisOrm
|
|
304
314
|
@@associations[self.model_name]
|
305
315
|
end
|
306
316
|
|
317
|
+
# is called from RedisOrm::Associations::HasMany to correctly save indices for associated records
|
318
|
+
def get_indices
|
319
|
+
@@indices[self.model_name]
|
320
|
+
end
|
321
|
+
|
307
322
|
def initialize(attributes = {}, id = nil, persisted = false)
|
308
323
|
@persisted = persisted
|
309
324
|
|
@@ -347,6 +362,7 @@ module RedisOrm
|
|
347
362
|
next if ! self.send(:"#{prop[:name]}_changed?")
|
348
363
|
|
349
364
|
prev_prop_value = instance_variable_get(:"@#{prop[:name]}_changes").first
|
365
|
+
prop_value = instance_variable_get(:"@#{prop[:name]}")
|
350
366
|
|
351
367
|
indices = @@indices[model_name].inject([]) do |sum, models_index|
|
352
368
|
if models_index[:name].is_a?(Array)
|
@@ -378,6 +394,32 @@ module RedisOrm
|
|
378
394
|
key_to_delete = "#{model_name}:#{prop[:name]}:#{prev_prop_value}"
|
379
395
|
$redis.del key_to_delete
|
380
396
|
end
|
397
|
+
|
398
|
+
# also we need to delete associated records *indices*
|
399
|
+
if !@@associations[model_name].empty?
|
400
|
+
@@associations[model_name].each do |assoc|
|
401
|
+
if :belongs_to == assoc[:type]
|
402
|
+
if !self.send(assoc[:foreign_model]).nil?
|
403
|
+
if index[:name].is_a?(Array)
|
404
|
+
keys_to_delete = if index[:name].index(prop) == 0
|
405
|
+
$redis.keys "#{assoc[:foreign_model]}:#{self.send(assoc[:foreign_model]).id}:#{model_name.to_s.pluralize}:#{prop[:name]}#{prev_prop_value}*"
|
406
|
+
else
|
407
|
+
$redis.keys "#{assoc[:foreign_model]}:#{self.send(assoc[:foreign_model]).id}:#{model_name.to_s.pluralize}:*#{prop[:name]}:#{prev_prop_value}*"
|
408
|
+
end
|
409
|
+
|
410
|
+
keys_to_delete.each{|key| $redis.del(key)}
|
411
|
+
else
|
412
|
+
beginning_of_the_key = "#{assoc[:foreign_model]}:#{self.send(assoc[:foreign_model]).id}:#{model_name.to_s.pluralize}:#{prop[:name]}:"
|
413
|
+
|
414
|
+
$redis.del(beginning_of_the_key + prev_prop_value.to_s)
|
415
|
+
|
416
|
+
index[:options][:unique] ? $redis.set((beginning_of_the_key + prop_value.to_s), @id) : $redis.zadd((beginning_of_the_key + prop_value.to_s), Time.now.to_f, @id)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end # deleting associated records *indices*
|
422
|
+
|
381
423
|
end
|
382
424
|
end
|
383
425
|
end
|
@@ -406,13 +448,15 @@ module RedisOrm
|
|
406
448
|
if prop_value.nil? && !prop[:options][:default].nil?
|
407
449
|
prop_value = prop[:options][:default]
|
408
450
|
# set instance variable in order to properly save indexes here
|
409
|
-
self.instance_variable_set(:"@#{prop[:name]}", prop[:options][:default])
|
451
|
+
self.instance_variable_set(:"@#{prop[:name]}", prop[:options][:default])
|
452
|
+
instance_variable_set :"@#{prop[:name]}_changes", [prop[:options][:default]]
|
410
453
|
end
|
411
454
|
|
412
455
|
$redis.hset("#{model_name}:#{id}", prop[:name].to_s, prop_value)
|
413
456
|
|
414
457
|
# reducing @#{prop[:name]}_changes array to the last value
|
415
458
|
prop_changes = instance_variable_get :"@#{prop[:name]}_changes"
|
459
|
+
|
416
460
|
if prop_changes && prop_changes.size > 2
|
417
461
|
instance_variable_set :"@#{prop[:name]}_changes", [prop_changes.last]
|
418
462
|
end
|
@@ -468,6 +512,27 @@ module RedisOrm
|
|
468
512
|
|
469
513
|
$redis.zrem "#{model_name}:ids", @id
|
470
514
|
|
515
|
+
# also we need to delete *indices* of associated records
|
516
|
+
if !@@associations[model_name].empty?
|
517
|
+
@@associations[model_name].each do |assoc|
|
518
|
+
if :belongs_to == assoc[:type]
|
519
|
+
if !self.send(assoc[:foreign_model]).nil?
|
520
|
+
@@indices[model_name].each do |index|
|
521
|
+
keys_to_delete = if index[:name].is_a?(Array)
|
522
|
+
full_index = index[:name].inject([]){|sum, index_part| sum << index_part}.join(':')
|
523
|
+
$redis.keys "#{assoc[:foreign_model]}:#{self.send(assoc[:foreign_model]).id}:#{model_name.to_s.pluralize}:#{full_index}:*"
|
524
|
+
else
|
525
|
+
["#{assoc[:foreign_model]}:#{self.send(assoc[:foreign_model]).id}:#{model_name.to_s.pluralize}:#{index[:name]}:#{self.send(index[:name])}"]
|
526
|
+
end
|
527
|
+
keys_to_delete.each do |key|
|
528
|
+
index[:options][:unique] ? $redis.del(key) : $redis.zrem(key, @id)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
471
536
|
# also we need to delete *links* to associated records
|
472
537
|
if !@@associations[model_name].empty?
|
473
538
|
@@associations[model_name].each do |assoc|
|
@@ -515,7 +580,7 @@ module RedisOrm
|
|
515
580
|
if @@associations[foreign_model].detect{|h| h[:type] == :belongs_to && h[:foreign_model] == model_name.to_sym}
|
516
581
|
$redis.del "#{foreign_model}:#{record.id}:#{model_name}"
|
517
582
|
end
|
518
|
-
|
583
|
+
|
519
584
|
if @@associations[foreign_model].detect{|h| h[:type] == :has_one && h[:foreign_model] == model_name.to_sym}
|
520
585
|
$redis.del "#{foreign_model}:#{record.id}:#{model_name}"
|
521
586
|
end
|
@@ -534,9 +599,9 @@ module RedisOrm
|
|
534
599
|
end
|
535
600
|
end
|
536
601
|
end
|
537
|
-
end
|
602
|
+
end
|
538
603
|
|
539
|
-
#
|
604
|
+
# remove all associated indices
|
540
605
|
@@indices[model_name].each do |index|
|
541
606
|
prepared_index = construct_prepared_index(index) # instance method not class one!
|
542
607
|
|
@@ -546,7 +611,7 @@ module RedisOrm
|
|
546
611
|
$redis.zremrangebyscore(prepared_index, 0, Time.now.to_f)
|
547
612
|
end
|
548
613
|
end
|
549
|
-
|
614
|
+
|
550
615
|
@@callbacks[model_name][:after_destroy].each do |callback|
|
551
616
|
self.send(callback)
|
552
617
|
end
|
data/redis_orm.gemspec
CHANGED
@@ -2,22 +2,22 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{redis_orm}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.5"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = [%q{Dmitrii Samoilov}]
|
9
|
-
s.date = %q{2011-
|
10
|
-
s.description = %q{ORM for Redis advanced key-value storage}
|
9
|
+
s.date = %q{2011-07-01}
|
10
|
+
s.description = %q{ORM for Redis (advanced key-value storage) with ActiveRecord API}
|
11
11
|
s.email = %q{germaninthetown@gmail.com}
|
12
|
-
s.extra_rdoc_files = [%q{CHANGELOG}, %q{LICENSE}, %q{README.md}, %q{lib/redis_orm.rb}, %q{lib/redis_orm/active_model_behavior.rb}, %q{lib/redis_orm/associations/belongs_to.rb}, %q{lib/redis_orm/associations/has_many.rb}, %q{lib/redis_orm/associations/has_many_proxy.rb}, %q{lib/redis_orm/associations/has_one.rb}, %q{lib/redis_orm/redis_orm.rb}]
|
13
|
-
s.files = [%q{CHANGELOG}, %q{LICENSE}, %q{Manifest}, %q{README.md}, %q{Rakefile}, %q{lib/redis_orm.rb}, %q{lib/redis_orm/active_model_behavior.rb}, %q{lib/redis_orm/associations/belongs_to.rb}, %q{lib/redis_orm/associations/has_many.rb}, %q{lib/redis_orm/associations/has_many_proxy.rb}, %q{lib/redis_orm/associations/has_one.rb}, %q{lib/redis_orm/redis_orm.rb}, %q{redis_orm.gemspec}, %q{test/associations_test.rb}, %q{test/atomicity_test.rb}, %q{test/basic_functionality_test.rb}, %q{test/callbacks_test.rb}, %q{test/changes_array_test.rb}, %q{test/dynamic_finders_test.rb}, %q{test/exceptions_test.rb}, %q{test/has_one_has_many_test.rb}, %q{test/indices_test.rb}, %q{test/options_test.rb}, %q{test/polymorphic_test.rb}, %q{test/redis.conf}, %q{test/test_helper.rb}, %q{test/validations_test.rb}]
|
12
|
+
s.extra_rdoc_files = [%q{CHANGELOG}, %q{LICENSE}, %q{README.md}, %q{lib/redis_orm.rb}, %q{lib/redis_orm/active_model_behavior.rb}, %q{lib/redis_orm/associations/belongs_to.rb}, %q{lib/redis_orm/associations/has_many.rb}, %q{lib/redis_orm/associations/has_many_helper.rb}, %q{lib/redis_orm/associations/has_many_proxy.rb}, %q{lib/redis_orm/associations/has_one.rb}, %q{lib/redis_orm/redis_orm.rb}]
|
13
|
+
s.files = [%q{CHANGELOG}, %q{LICENSE}, %q{Manifest}, %q{README.md}, %q{Rakefile}, %q{lib/redis_orm.rb}, %q{lib/redis_orm/active_model_behavior.rb}, %q{lib/redis_orm/associations/belongs_to.rb}, %q{lib/redis_orm/associations/has_many.rb}, %q{lib/redis_orm/associations/has_many_helper.rb}, %q{lib/redis_orm/associations/has_many_proxy.rb}, %q{lib/redis_orm/associations/has_one.rb}, %q{lib/redis_orm/redis_orm.rb}, %q{redis_orm.gemspec}, %q{test/association_indices_test.rb}, %q{test/associations_test.rb}, %q{test/atomicity_test.rb}, %q{test/basic_functionality_test.rb}, %q{test/callbacks_test.rb}, %q{test/changes_array_test.rb}, %q{test/dynamic_finders_test.rb}, %q{test/exceptions_test.rb}, %q{test/has_one_has_many_test.rb}, %q{test/indices_test.rb}, %q{test/options_test.rb}, %q{test/polymorphic_test.rb}, %q{test/redis.conf}, %q{test/test_helper.rb}, %q{test/validations_test.rb}]
|
14
14
|
s.homepage = %q{https://github.com/german/redis_orm}
|
15
15
|
s.rdoc_options = [%q{--line-numbers}, %q{--inline-source}, %q{--title}, %q{Redis_orm}, %q{--main}, %q{README.md}]
|
16
16
|
s.require_paths = [%q{lib}]
|
17
17
|
s.rubyforge_project = %q{redis_orm}
|
18
18
|
s.rubygems_version = %q{1.8.5}
|
19
|
-
s.summary = %q{ORM for Redis advanced key-value storage}
|
20
|
-
s.test_files = [%q{test/options_test.rb}, %q{test/dynamic_finders_test.rb}, %q{test/associations_test.rb}, %q{test/validations_test.rb}, %q{test/test_helper.rb}, %q{test/polymorphic_test.rb}, %q{test/atomicity_test.rb}, %q{test/exceptions_test.rb}, %q{test/has_one_has_many_test.rb}, %q{test/indices_test.rb}, %q{test/changes_array_test.rb}, %q{test/callbacks_test.rb}, %q{test/basic_functionality_test.rb}]
|
19
|
+
s.summary = %q{ORM for Redis (advanced key-value storage) with ActiveRecord API}
|
20
|
+
s.test_files = [%q{test/options_test.rb}, %q{test/dynamic_finders_test.rb}, %q{test/associations_test.rb}, %q{test/validations_test.rb}, %q{test/test_helper.rb}, %q{test/polymorphic_test.rb}, %q{test/atomicity_test.rb}, %q{test/exceptions_test.rb}, %q{test/association_indices_test.rb}, %q{test/has_one_has_many_test.rb}, %q{test/indices_test.rb}, %q{test/changes_array_test.rb}, %q{test/callbacks_test.rb}, %q{test/basic_functionality_test.rb}]
|
21
21
|
|
22
22
|
if s.respond_to? :specification_version then
|
23
23
|
s.specification_version = 3
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require File.dirname(File.expand_path(__FILE__)) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class Article < RedisOrm::Base
|
4
|
+
property :title, String
|
5
|
+
has_many :comments
|
6
|
+
end
|
7
|
+
|
8
|
+
class Comment < RedisOrm::Base
|
9
|
+
property :body, String
|
10
|
+
|
11
|
+
property :moderated, RedisOrm::Boolean, :default => false
|
12
|
+
index :moderated
|
13
|
+
|
14
|
+
belongs_to :article
|
15
|
+
end
|
16
|
+
|
17
|
+
class User < RedisOrm::Base
|
18
|
+
property :name, String
|
19
|
+
index :name
|
20
|
+
|
21
|
+
property :moderator, RedisOrm::Boolean, :default => false
|
22
|
+
property :moderated_area, String, :default => "messages"
|
23
|
+
|
24
|
+
index :moderator
|
25
|
+
index [:moderator, :moderated_area]
|
26
|
+
|
27
|
+
has_many :users, :as => :friends
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "check indices for associations" do
|
31
|
+
before(:each) do
|
32
|
+
@article = Article.new :title => "DHH drops OpenID on 37signals"
|
33
|
+
@article.save
|
34
|
+
|
35
|
+
@article.should be
|
36
|
+
@article.title.should == "DHH drops OpenID on 37signals"
|
37
|
+
|
38
|
+
@comment1 = Comment.new :body => "test"
|
39
|
+
@comment1.save
|
40
|
+
@comment1.should be
|
41
|
+
@comment1.body.should == "test"
|
42
|
+
@comment1.moderated.should == false
|
43
|
+
|
44
|
+
@comment2 = Comment.new :body => "test #2", :moderated => true
|
45
|
+
@comment2.save
|
46
|
+
@comment2.should be
|
47
|
+
@comment2.body.should == "test #2"
|
48
|
+
@comment2.moderated.should == true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should properly find associated records (e.g. with :conditions, :order, etc options) '<<' used for association" do
|
52
|
+
@article.comments << [@comment1, @comment2]
|
53
|
+
@article.comments.count.should == 2
|
54
|
+
|
55
|
+
@article.comments.all(:limit => 1).size.should == 1
|
56
|
+
@article.comments.find(:first).should be
|
57
|
+
@article.comments.find(:first).id.should == @comment1.id
|
58
|
+
@article.comments.find(:last).should be
|
59
|
+
@article.comments.find(:last).id.should == @comment2.id
|
60
|
+
|
61
|
+
@article.comments.find(:all, :conditions => {:moderated => true}).size.should == 1
|
62
|
+
@article.comments.find(:all, :conditions => {:moderated => false}).size.should == 1
|
63
|
+
@article.comments.find(:all, :conditions => {:moderated => true})[0].id.should == @comment2.id
|
64
|
+
@article.comments.find(:all, :conditions => {:moderated => false})[0].id.should == @comment1.id
|
65
|
+
|
66
|
+
@article.comments.find(:all, :conditions => {:moderated => true}, :limit => 1).size.should == 1
|
67
|
+
@article.comments.find(:all, :conditions => {:moderated => false}, :limit => 1).size.should == 1
|
68
|
+
@article.comments.find(:all, :conditions => {:moderated => true}, :limit => 1)[0].id.should == @comment2.id
|
69
|
+
@article.comments.find(:all, :conditions => {:moderated => false}, :limit => 1)[0].id.should == @comment1.id
|
70
|
+
|
71
|
+
@article.comments.find(:all, :conditions => {:moderated => true}, :limit => 1, :order => :desc).size.should == 1
|
72
|
+
@article.comments.find(:all, :conditions => {:moderated => false}, :limit => 1, :order => :asc).size.should == 1
|
73
|
+
@article.comments.find(:all, :conditions => {:moderated => true}, :limit => 1, :order => :desc)[0].id.should == @comment2.id
|
74
|
+
@article.comments.find(:all, :conditions => {:moderated => false}, :limit => 1, :order => :asc)[0].id.should == @comment1.id
|
75
|
+
|
76
|
+
@comment1.update_attribute :moderated, true
|
77
|
+
@article.comments.find(:all, :conditions => {:moderated => true}).size.should == 2
|
78
|
+
@article.comments.find(:all, :conditions => {:moderated => false}).size.should == 0
|
79
|
+
|
80
|
+
@comment1.destroy
|
81
|
+
$redis.zrange("article:#{@article.id}:comments:moderated:true", 0, -1).size.should == 1
|
82
|
+
$redis.zrange("article:#{@article.id}:comments:moderated:true", 0, -1)[0].should == @comment2.id.to_s
|
83
|
+
$redis.zrange("article:#{@article.id}:comments:moderated:false", 0, -1).size.should == 0
|
84
|
+
@article.comments.find(:all, :conditions => {:moderated => true}).size.should == 1
|
85
|
+
@article.comments.find(:all, :conditions => {:moderated => false}).size.should == 0
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should properly find associated records (e.g. with :conditions, :order, etc options) '=' used for association" do
|
89
|
+
@article.comments = [@comment1, @comment2]
|
90
|
+
@article.comments.count.should == 2
|
91
|
+
|
92
|
+
@article.comments.all(:limit => 1).size.should == 1
|
93
|
+
@article.comments.find(:first).should be
|
94
|
+
@article.comments.find(:first).id.should == @comment1.id
|
95
|
+
@article.comments.find(:last).should be
|
96
|
+
@article.comments.find(:last).id.should == @comment2.id
|
97
|
+
|
98
|
+
@article.comments.find(:all, :conditions => {:moderated => true}).size.should == 1
|
99
|
+
@article.comments.find(:all, :conditions => {:moderated => false}).size.should == 1
|
100
|
+
@article.comments.find(:all, :conditions => {:moderated => true})[0].id.should == @comment2.id
|
101
|
+
@article.comments.find(:all, :conditions => {:moderated => false})[0].id.should == @comment1.id
|
102
|
+
|
103
|
+
@article.comments.find(:all, :conditions => {:moderated => true}, :limit => 1).size.should == 1
|
104
|
+
@article.comments.find(:all, :conditions => {:moderated => false}, :limit => 1).size.should == 1
|
105
|
+
@article.comments.find(:all, :conditions => {:moderated => true}, :limit => 1)[0].id.should == @comment2.id
|
106
|
+
@article.comments.find(:all, :conditions => {:moderated => false}, :limit => 1)[0].id.should == @comment1.id
|
107
|
+
|
108
|
+
@article.comments.find(:all, :conditions => {:moderated => true}, :limit => 1, :order => :desc).size.should == 1
|
109
|
+
@article.comments.find(:all, :conditions => {:moderated => false}, :limit => 1, :order => :asc).size.should == 1
|
110
|
+
@article.comments.find(:all, :conditions => {:moderated => true}, :limit => 1, :order => :desc)[0].id.should == @comment2.id
|
111
|
+
@article.comments.find(:all, :conditions => {:moderated => false}, :limit => 1, :order => :asc)[0].id.should == @comment1.id
|
112
|
+
|
113
|
+
@comment1.update_attribute :moderated, true
|
114
|
+
@article.comments.find(:all, :conditions => {:moderated => true}).size.should == 2
|
115
|
+
@article.comments.find(:all, :conditions => {:moderated => false}).size.should == 0
|
116
|
+
|
117
|
+
@comment1.destroy
|
118
|
+
@article.comments.find(:all, :conditions => {:moderated => true}).size.should == 1
|
119
|
+
@article.comments.find(:all, :conditions => {:moderated => false}).size.should == 0
|
120
|
+
$redis.zrange("article:#{@article.id}:comments:moderated:true", 0, -1).size.should == 1
|
121
|
+
$redis.zrange("article:#{@article.id}:comments:moderated:true", 0, -1)[0].should == @comment2.id.to_s
|
122
|
+
$redis.zrange("article:#{@article.id}:comments:moderated:false", 0, -1).size.should == 0
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should check compound indices for associations" do
|
126
|
+
friend1 = User.create :name => "Director", :moderator => true, :moderated_area => "films"
|
127
|
+
friend2 = User.create :name => "Admin", :moderator => true, :moderated_area => "all"
|
128
|
+
friend3 = User.create :name => "Gena", :moderator => false
|
129
|
+
|
130
|
+
me = User.create :name => "german"
|
131
|
+
|
132
|
+
me.friends << [friend1, friend2, friend3]
|
133
|
+
|
134
|
+
me.friends.count.should == 3
|
135
|
+
me.friends.find(:all, :conditions => {:moderator => true}).size.should == 2
|
136
|
+
me.friends.find(:all, :conditions => {:moderator => false}).size.should == 1
|
137
|
+
|
138
|
+
me.friends.find(:all, :conditions => {:moderator => true, :moderated_area => "films"}).size.should == 1
|
139
|
+
me.friends.find(:all, :conditions => {:moderator => true, :moderated_area => "films"})[0].id.should == friend1.id
|
140
|
+
|
141
|
+
# reverse key's order in :conditions hash
|
142
|
+
me.friends.find(:all, :conditions => {:moderated_area => "all", :moderator => true}).size.should == 1
|
143
|
+
me.friends.find(:all, :conditions => {:moderated_area => "all", :moderator => true})[0].id.should == friend2.id
|
144
|
+
end
|
145
|
+
end
|
data/test/associations_test.rb
CHANGED
@@ -8,6 +8,7 @@ end
|
|
8
8
|
|
9
9
|
class Comment < RedisOrm::Base
|
10
10
|
property :body, String
|
11
|
+
|
11
12
|
belongs_to :article
|
12
13
|
end
|
13
14
|
|
@@ -28,7 +29,7 @@ end
|
|
28
29
|
|
29
30
|
class User < RedisOrm::Base
|
30
31
|
property :name, String
|
31
|
-
index :name
|
32
|
+
index :name
|
32
33
|
has_many :users, :as => :friends
|
33
34
|
end
|
34
35
|
|
@@ -39,21 +40,18 @@ end
|
|
39
40
|
|
40
41
|
describe "check associations" do
|
41
42
|
before(:each) do
|
42
|
-
@article = Article.new
|
43
|
-
@article.title = "DHH drops OpenID on 37signals"
|
43
|
+
@article = Article.new :title => "DHH drops OpenID on 37signals"
|
44
44
|
@article.save
|
45
45
|
|
46
46
|
@article.should be
|
47
47
|
@article.title.should == "DHH drops OpenID on 37signals"
|
48
48
|
|
49
|
-
@comment1 = Comment.new
|
50
|
-
@comment1.body = "test"
|
49
|
+
@comment1 = Comment.new :body => "test"
|
51
50
|
@comment1.save
|
52
51
|
@comment1.should be
|
53
52
|
@comment1.body.should == "test"
|
54
53
|
|
55
|
-
@comment2 = Comment.new
|
56
|
-
@comment2.body = "test #2"
|
54
|
+
@comment2 = Comment.new :body => "test #2"
|
57
55
|
@comment2.save
|
58
56
|
@comment2.should be
|
59
57
|
@comment2.body.should == "test #2"
|
@@ -111,7 +109,7 @@ describe "check associations" do
|
|
111
109
|
chicago.profiles.count.should == 0
|
112
110
|
end
|
113
111
|
|
114
|
-
it "should return array" do
|
112
|
+
it "should return array of records for has_many association" do
|
115
113
|
@article.comments << []
|
116
114
|
@article.comments.count.should == 0
|
117
115
|
|
@@ -177,7 +175,6 @@ describe "check associations" do
|
|
177
175
|
@article.destroy
|
178
176
|
|
179
177
|
Article.count.should == 0
|
180
|
-
|
181
178
|
Comment.count.should == 2
|
182
179
|
end
|
183
180
|
|
@@ -38,7 +38,7 @@ describe "check associations" do
|
|
38
38
|
|
39
39
|
User.find_all_by_first_name_and_last_name('Dmitrii', 'Samoilov').size.should == 1
|
40
40
|
|
41
|
-
|
41
|
+
User.find_all_by_last_name_and_first_name('Samoilov', 'Dmitrii')[0].id.should == user1.id
|
42
42
|
|
43
43
|
lambda{User.find_by_first_name_and_cast_name('Dmitrii', 'Samoilov')}.should raise_error
|
44
44
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis_orm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.5'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-07-01 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement: &
|
16
|
+
requirement: &84648260 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 3.0.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *84648260
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activemodel
|
27
|
-
requirement: &
|
27
|
+
requirement: &84647900 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 3.0.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *84647900
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: redis
|
38
|
-
requirement: &
|
38
|
+
requirement: &84647510 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 2.2.0
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *84647510
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &84647120 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,8 +54,8 @@ dependencies:
|
|
54
54
|
version: 2.5.0
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
58
|
-
description: ORM for Redis advanced key-value storage
|
57
|
+
version_requirements: *84647120
|
58
|
+
description: ORM for Redis (advanced key-value storage) with ActiveRecord API
|
59
59
|
email: germaninthetown@gmail.com
|
60
60
|
executables: []
|
61
61
|
extensions: []
|
@@ -67,6 +67,7 @@ extra_rdoc_files:
|
|
67
67
|
- lib/redis_orm/active_model_behavior.rb
|
68
68
|
- lib/redis_orm/associations/belongs_to.rb
|
69
69
|
- lib/redis_orm/associations/has_many.rb
|
70
|
+
- lib/redis_orm/associations/has_many_helper.rb
|
70
71
|
- lib/redis_orm/associations/has_many_proxy.rb
|
71
72
|
- lib/redis_orm/associations/has_one.rb
|
72
73
|
- lib/redis_orm/redis_orm.rb
|
@@ -80,10 +81,12 @@ files:
|
|
80
81
|
- lib/redis_orm/active_model_behavior.rb
|
81
82
|
- lib/redis_orm/associations/belongs_to.rb
|
82
83
|
- lib/redis_orm/associations/has_many.rb
|
84
|
+
- lib/redis_orm/associations/has_many_helper.rb
|
83
85
|
- lib/redis_orm/associations/has_many_proxy.rb
|
84
86
|
- lib/redis_orm/associations/has_one.rb
|
85
87
|
- lib/redis_orm/redis_orm.rb
|
86
88
|
- redis_orm.gemspec
|
89
|
+
- test/association_indices_test.rb
|
87
90
|
- test/associations_test.rb
|
88
91
|
- test/atomicity_test.rb
|
89
92
|
- test/basic_functionality_test.rb
|
@@ -127,7 +130,7 @@ rubyforge_project: redis_orm
|
|
127
130
|
rubygems_version: 1.8.5
|
128
131
|
signing_key:
|
129
132
|
specification_version: 3
|
130
|
-
summary: ORM for Redis advanced key-value storage
|
133
|
+
summary: ORM for Redis (advanced key-value storage) with ActiveRecord API
|
131
134
|
test_files:
|
132
135
|
- test/options_test.rb
|
133
136
|
- test/dynamic_finders_test.rb
|
@@ -137,6 +140,7 @@ test_files:
|
|
137
140
|
- test/polymorphic_test.rb
|
138
141
|
- test/atomicity_test.rb
|
139
142
|
- test/exceptions_test.rb
|
143
|
+
- test/association_indices_test.rb
|
140
144
|
- test/has_one_has_many_test.rb
|
141
145
|
- test/indices_test.rb
|
142
146
|
- test/changes_array_test.rb
|