redis_orm 0.4.2 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
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 3 options for now (#find is an alias for #all in has_many proxy):
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).should == []
102
- @album.photos.all(:limit => 1, :offset => 0).size.should == 1
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.4.2') do |p|
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
- record_ids = if options[:order].to_s == 'desc'
77
- $redis.zrevrangebyscore(__key__, Time.now.to_f, 0, :limit => limit)
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
- $redis.zrangebyscore(__key__, 0, Time.now.to_f, :limit => limit)
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 = @foreign_models.to_s.singularize.camelize.constantize.find(record_ids)
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] == 'asc' ? 'desc' : 'asc'
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
@@ -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 TODO
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
- models_index[:name] == properties.map{|p| p.to_sym}
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].to_sym
141
+ models_index[:name] == properties[0]
137
142
  end
138
143
  end
139
144
  end
140
145
 
141
- def construct_prepared_index(index, properties_hash)
146
+ def construct_prepared_index(index, conditions_hash)
142
147
  prepared_index = model_name.to_s
143
148
 
144
- properties_hash.each do |key, value|
145
- # raise if User.find_by_firstname_and_castname => there's no *castname* in User's properties
146
- raise ArgumentsMismatch if !@@properties[model_name].detect{|p| p[:name] == key.to_sym}
147
- prepared_index += ":#{key}:#{value}"
148
- end
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
- # we need to ensure that smembers are correct after removal of the record
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.4.2"
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-06-25}
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
@@ -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
- lambda{User.find_all_by_last_name_and_first_name('Samoilov', 'Dmitrii')}.should raise_error
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.2
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-06-25 00:00:00.000000000Z
12
+ date: 2011-07-01 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &82547900 !ruby/object:Gem::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: *82547900
24
+ version_requirements: *84648260
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activemodel
27
- requirement: &82547630 !ruby/object:Gem::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: *82547630
35
+ version_requirements: *84647900
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: redis
38
- requirement: &82547320 !ruby/object:Gem::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: *82547320
46
+ version_requirements: *84647510
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &82547020 !ruby/object:Gem::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: *82547020
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