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 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