sohm 0.0.1 → 0.9.0

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/lib/sohm.rb CHANGED
@@ -33,7 +33,7 @@ module Sohm
33
33
  # Raised when trying to save an object with a `unique` index for
34
34
  # which the value already exists.
35
35
  #
36
- # Solution: rescue `Ohm::UniqueIndexViolation` during save, but
36
+ # Solution: rescue `Sohm::UniqueIndexViolation` during save, but
37
37
  # also, do some validations even before attempting to save.
38
38
  #
39
39
  class Error < StandardError; end
@@ -53,14 +53,14 @@ module Sohm
53
53
  #
54
54
  # Example:
55
55
  #
56
- # class Comment < Ohm::Model
56
+ # class Comment < Sohm::Model
57
57
  # reference :user, User # NameError undefined constant User.
58
58
  # end
59
59
  #
60
60
  # # Instead of relying on some clever `const_missing` hack, we can
61
61
  # # simply use a symbol or a string.
62
62
  #
63
- # class Comment < Ohm::Model
63
+ # class Comment < Sohm::Model
64
64
  # reference :user, :User
65
65
  # reference :post, "Post"
66
66
  # end
@@ -76,18 +76,6 @@ module Sohm
76
76
  def self.dict(arr)
77
77
  Hash[*arr]
78
78
  end
79
-
80
- def self.sort(redis, key, options)
81
- args = []
82
-
83
- args.concat(["BY", options[:by]]) if options[:by]
84
- args.concat(["GET", options[:get]]) if options[:get]
85
- args.concat(["LIMIT"] + options[:limit]) if options[:limit]
86
- args.concat(options[:order].split(" ")) if options[:order]
87
- args.concat(["STORE", options[:store]]) if options[:store]
88
-
89
- redis.call("SORT", key, *args)
90
- end
91
79
  end
92
80
 
93
81
  # Use this if you want to do quick ad hoc redis commands against the
@@ -98,14 +86,28 @@ module Sohm
98
86
  # Ohm.redis.call("SET", "foo", "bar")
99
87
  # Ohm.redis.call("FLUSH")
100
88
  #
89
+ @redis = Redic.new
101
90
  def self.redis
102
- @redis ||= Redic.new
91
+ @redis
103
92
  end
104
93
 
105
94
  def self.redis=(redis)
106
95
  @redis = redis
107
96
  end
108
97
 
98
+ # If you are using a Redis pool to override @redis above, chances are
99
+ # you won't need a mutex(since your opertions will run on different
100
+ # redis instances), so you can use this to override the default mutex
101
+ # for better performance
102
+ @mutex = Mutex.new
103
+ def self.mutex=(mutex)
104
+ @mutex = mutex
105
+ end
106
+
107
+ def self.mutex
108
+ @mutex
109
+ end
110
+
109
111
  # By default, EVALSHA is used
110
112
  def self.enable_evalsha
111
113
  defined?(@enable_evalsha) ? @enable_evalsha : true
@@ -137,7 +139,6 @@ module Sohm
137
139
  size == 0
138
140
  end
139
141
 
140
- # TODO: fix this later
141
142
  # Wraps the whole pipelining functionality.
142
143
  def fetch(ids)
143
144
  data = nil
@@ -154,7 +155,7 @@ module Sohm
154
155
 
155
156
  [].tap do |result|
156
157
  data.each_with_index do |atts, idx|
157
- unless attrs.empty?
158
+ unless atts.empty?
158
159
  result << model.new(Utils.dict(atts).update(:id => ids[idx]))
159
160
  end
160
161
  end
@@ -195,10 +196,10 @@ module Sohm
195
196
  #
196
197
  # Example:
197
198
  #
198
- # class Comment < Ohm::Model
199
+ # class Comment < Sohm::Model
199
200
  # end
200
201
  #
201
- # class Post < Ohm::Model
202
+ # class Post < Sohm::Model
202
203
  # list :comments, :Comment
203
204
  # end
204
205
  #
@@ -246,10 +247,10 @@ module Sohm
246
247
  #
247
248
  # Example:
248
249
  #
249
- # class Comment < Ohm::Model
250
+ # class Comment < Sohm::Model
250
251
  # end
251
252
  #
252
- # class Post < Ohm::Model
253
+ # class Post < Sohm::Model
253
254
  # list :comments, :Comment
254
255
  # end
255
256
  #
@@ -272,10 +273,10 @@ module Sohm
272
273
 
273
274
  # Returns an array with all the ID's of the list.
274
275
  #
275
- # class Comment < Ohm::Model
276
+ # class Comment < Sohm::Model
276
277
  # end
277
278
  #
278
- # class Post < Ohm::Model
279
+ # class Post < Sohm::Model
279
280
  # list :comments, :Comment
280
281
  # end
281
282
  #
@@ -305,57 +306,6 @@ module Sohm
305
306
  class BasicSet
306
307
  include Collection
307
308
 
308
- # Allows you to sort by any attribute in the hash, this doesn't include
309
- # the +id+. If you want to sort by ID, use #sort.
310
- #
311
- # class User < Ohm::Model
312
- # attribute :name
313
- # end
314
- #
315
- # User.all.sort_by(:name, :order => "ALPHA")
316
- # User.all.sort_by(:name, :order => "ALPHA DESC")
317
- # User.all.sort_by(:name, :order => "ALPHA DESC", :limit => [0, 10])
318
- #
319
- # Note: This is slower compared to just doing `sort`, specifically
320
- # because Redis has to read each individual hash in order to sort
321
- # them.
322
- #
323
- def sort_by(att, options = {})
324
- sort(options.merge(:by => to_key(att)))
325
- end
326
-
327
- # Allows you to sort your models using their IDs. This is much
328
- # faster than `sort_by`. If you simply want to get records in
329
- # ascending or descending order, then this is the best method to
330
- # do that.
331
- #
332
- # Example:
333
- #
334
- # class User < Ohm::Model
335
- # attribute :name
336
- # end
337
- #
338
- # User.create(:name => "John")
339
- # User.create(:name => "Jane")
340
- #
341
- # User.all.sort.map(&:id) == ["1", "2"]
342
- # # => true
343
- #
344
- # User.all.sort(:order => "ASC").map(&:id) == ["1", "2"]
345
- # # => true
346
- #
347
- # User.all.sort(:order => "DESC").map(&:id) == ["2", "1"]
348
- # # => true
349
- #
350
- def sort(options = {})
351
- if options.has_key?(:get)
352
- options[:get] = to_key(options[:get])
353
- return execute { |key| Utils.sort(redis, key, options) }
354
- end
355
-
356
- fetch(execute { |key| Utils.sort(redis, key, options) })
357
- end
358
-
359
309
  # Check if a model is included in this set.
360
310
  #
361
311
  # Example:
@@ -377,34 +327,28 @@ module Sohm
377
327
  execute { |key| redis.call("SCARD", key) }
378
328
  end
379
329
 
380
- # Syntactic sugar for `sort_by` or `sort` when you only need the
381
- # first element.
330
+ # SMEMBERS then choosing the first will take too much memory in case data
331
+ # grow big enough, which will be slow in this case.
332
+ # Providing +sample+ only gives a hint that we won't preserve any order
333
+ # for this, there will be 2 cases:
382
334
  #
383
- # Example:
335
+ # 1. Anyone in the set will do, this is the original use case of +sample*
336
+ # 2. For some reasons(maybe due to filters), we only have 1 element left
337
+ # in this set, using +sample+ will do the trick
384
338
  #
385
- # User.all.first ==
386
- # User.all.sort(:limit => [0, 1]).first
387
- #
388
- # User.all.first(:by => :name, "ALPHA") ==
389
- # User.all.sort_by(:name, :order => "ALPHA", :limit => [0, 1]).first
390
- #
391
- def first(options = {})
392
- opts = options.dup
393
- opts.merge!(:limit => [0, 1])
394
-
395
- if opts[:by]
396
- sort_by(opts.delete(:by), opts).first
397
- else
398
- sort(opts).first
399
- end
339
+ # For all the other cases, we won't be able to fetch a single element
340
+ # without fetching all elements first(in other words, doing this
341
+ # efficiently)
342
+ def sample
343
+ model[execute { |key| redis.call("SRANDMEMBER", key) }]
400
344
  end
401
345
 
402
346
  # Returns an array with all the ID's of the set.
403
347
  #
404
- # class Post < Ohm::Model
348
+ # class Post < Sohm::Model
405
349
  # end
406
350
  #
407
- # class User < Ohm::Model
351
+ # class User < Sohm::Model
408
352
  # attribute :name
409
353
  # index :name
410
354
  #
@@ -442,10 +386,10 @@ module Sohm
442
386
  #
443
387
  # Example:
444
388
  #
445
- # class Post < Ohm::Model
389
+ # class Post < Sohm::Model
446
390
  # end
447
391
  #
448
- # class User < Ohm::Model
392
+ # class User < Sohm::Model
449
393
  # set :posts, :Post
450
394
  # end
451
395
  #
@@ -459,15 +403,6 @@ module Sohm
459
403
  def exists?(id)
460
404
  execute { |key| redis.call("SISMEMBER", key, id) == 1 }
461
405
  end
462
-
463
- private
464
- def to_key(att)
465
- if model.counters.include?(att)
466
- namespace["*:counters->%s" % att]
467
- else
468
- namespace["*->%s" % att]
469
- end
470
- end
471
406
  end
472
407
 
473
408
  class Set < BasicSet
@@ -583,13 +518,13 @@ module Sohm
583
518
  #
584
519
  # Example:
585
520
  #
586
- # User.all.kind_of?(Ohm::Set)
521
+ # User.all.kind_of?(Sohm::Set)
587
522
  # # => true
588
523
  #
589
- # User.find(:name => "John").kind_of?(Ohm::Set)
524
+ # User.find(:name => "John").kind_of?(Sohm::Set)
590
525
  # # => true
591
526
  #
592
- # User.find(:name => "John", :age => 30).kind_of?(Ohm::MultiSet)
527
+ # User.find(:name => "John", :age => 30).kind_of?(Sohm::MultiSet)
593
528
  # # => true
594
529
  #
595
530
  class MultiSet < BasicSet
@@ -704,7 +639,7 @@ module Sohm
704
639
  #
705
640
  # Example:
706
641
  #
707
- # class User < Ohm::Model
642
+ # class User < Sohm::Model
708
643
  # attribute :name
709
644
  # index :name
710
645
  #
@@ -740,7 +675,7 @@ module Sohm
740
675
  #
741
676
  # Next we increment points:
742
677
  #
743
- # HINCR User:1:counters points 1
678
+ # HINCR User:1:_counters points 1
744
679
  #
745
680
  # And then we add a Post to the `posts` set.
746
681
  # (For brevity, let's assume the Post created has an ID of 1).
@@ -757,7 +692,7 @@ module Sohm
757
692
  end
758
693
 
759
694
  def self.mutex
760
- @@mutex ||= Mutex.new
695
+ Sohm.mutex
761
696
  end
762
697
 
763
698
  def self.synchronize(&block)
@@ -768,7 +703,7 @@ module Sohm
768
703
  #
769
704
  # Example:
770
705
  #
771
- # class User < Ohm::Model
706
+ # class User < Sohm::Model
772
707
  # end
773
708
  #
774
709
  # User.key == "User"
@@ -782,7 +717,7 @@ module Sohm
782
717
  # http://github.com/soveran/nido
783
718
  #
784
719
  def self.key
785
- @key ||= Nido.new(self.name)
720
+ Nido.new(self.name)
786
721
  end
787
722
 
788
723
  # Retrieve a record by ID.
@@ -807,7 +742,7 @@ module Sohm
807
742
  # Note: The use of this should be a last resort for your actual
808
743
  # application runtime, or for simply debugging in your console. If
809
744
  # you care about performance, you should pipeline your reads. For
810
- # more information checkout the implementation of Ohm::List#fetch.
745
+ # more information checkout the implementation of Sohm::List#fetch.
811
746
  #
812
747
  def self.to_proc
813
748
  lambda { |id| self[id] }
@@ -822,7 +757,7 @@ module Sohm
822
757
  #
823
758
  # Example:
824
759
  #
825
- # class User < Ohm::Model
760
+ # class User < Sohm::Model
826
761
  # attribute :email
827
762
  #
828
763
  # attribute :name
@@ -860,9 +795,9 @@ module Sohm
860
795
  keys = filters(dict)
861
796
 
862
797
  if keys.size == 1
863
- Ohm::Set.new(keys.first, key, self)
798
+ Sohm::Set.new(keys.first, key, self)
864
799
  else
865
- Ohm::MultiSet.new(key, self, Command.new(:sinterstore, *keys))
800
+ Sohm::MultiSet.new(key, self, Command.new(:sinterstore, *keys))
866
801
  end
867
802
  end
868
803
 
@@ -882,11 +817,11 @@ module Sohm
882
817
  indices << attribute unless indices.include?(attribute)
883
818
  end
884
819
 
885
- # Declare an Ohm::Set with the given name.
820
+ # Declare an Sohm::Set with the given name.
886
821
  #
887
822
  # Example:
888
823
  #
889
- # class User < Ohm::Model
824
+ # class User < Sohm::Model
890
825
  # set :posts, :Post
891
826
  # end
892
827
  #
@@ -895,7 +830,7 @@ module Sohm
895
830
  # # => true
896
831
  #
897
832
  # Note: You can't use the set until you save the model. If you try
898
- # to do it, you'll receive an Ohm::MissingID error.
833
+ # to do it, you'll receive an Sohm::MissingID error.
899
834
  #
900
835
  def self.set(name, model)
901
836
  track(name)
@@ -903,18 +838,18 @@ module Sohm
903
838
  define_method name do
904
839
  model = Utils.const(self.class, model)
905
840
 
906
- Ohm::MutableSet.new(key[name], model.key, model)
841
+ Sohm::MutableSet.new(key[name], model.key, model)
907
842
  end
908
843
  end
909
844
 
910
- # Declare an Ohm::List with the given name.
845
+ # Declare an Sohm::List with the given name.
911
846
  #
912
847
  # Example:
913
848
  #
914
- # class Comment < Ohm::Model
849
+ # class Comment < Sohm::Model
915
850
  # end
916
851
  #
917
- # class Post < Ohm::Model
852
+ # class Post < Sohm::Model
918
853
  # list :comments, :Comment
919
854
  # end
920
855
  #
@@ -925,7 +860,7 @@ module Sohm
925
860
  # # => true
926
861
  #
927
862
  # Note: You can't use the list until you save the model. If you try
928
- # to do it, you'll receive an Ohm::MissingID error.
863
+ # to do it, you'll receive an Sohm::MissingID error.
929
864
  #
930
865
  def self.list(name, model)
931
866
  track(name)
@@ -933,24 +868,24 @@ module Sohm
933
868
  define_method name do
934
869
  model = Utils.const(self.class, model)
935
870
 
936
- Ohm::List.new(key[name], model.key, model)
871
+ Sohm::List.new(key[name], model.key, model)
937
872
  end
938
873
  end
939
874
 
940
875
  # A macro for defining a method which basically does a find.
941
876
  #
942
877
  # Example:
943
- # class Post < Ohm::Model
878
+ # class Post < Sohm::Model
944
879
  # reference :user, :User
945
880
  # end
946
881
  #
947
- # class User < Ohm::Model
882
+ # class User < Sohm::Model
948
883
  # collection :posts, :Post
949
884
  # end
950
885
  #
951
886
  # # is the same as
952
887
  #
953
- # class User < Ohm::Model
888
+ # class User < Sohm::Model
954
889
  # def posts
955
890
  # Post.find(:user_id => self.id)
956
891
  # end
@@ -968,27 +903,25 @@ module Sohm
968
903
  #
969
904
  # Example:
970
905
  #
971
- # class Post < Ohm::Model
906
+ # class Post < Sohm::Model
972
907
  # reference :user, :User
973
908
  # end
974
909
  #
975
910
  # # It's the same as:
976
911
  #
977
- # class Post < Ohm::Model
912
+ # class Post < Sohm::Model
978
913
  # attribute :user_id
979
914
  # index :user_id
980
915
  #
981
916
  # def user
982
- # @_memo[:user] ||= User[user_id]
917
+ # User[user_id]
983
918
  # end
984
919
  #
985
920
  # def user=(user)
986
921
  # self.user_id = user.id
987
- # @_memo[:user] = user
988
922
  # end
989
923
  #
990
924
  # def user_id=(user_id)
991
- # @_memo.delete(:user_id)
992
925
  # self.user_id = user_id
993
926
  # end
994
927
  # end
@@ -1006,20 +939,16 @@ module Sohm
1006
939
  end
1007
940
 
1008
941
  define_method(writer) do |value|
1009
- @_memo.delete(name)
1010
942
  @attributes[reader] = value
1011
943
  end
1012
944
 
1013
945
  define_method(:"#{name}=") do |value|
1014
- @_memo.delete(name)
1015
946
  send(writer, value ? value.id : nil)
1016
947
  end
1017
948
 
1018
949
  define_method(name) do
1019
- @_memo[name] ||= begin
1020
- model = Utils.const(self.class, model)
1021
- model[send(reader)]
1022
- end
950
+ model = Utils.const(self.class, model)
951
+ model[send(reader)]
1023
952
  end
1024
953
  end
1025
954
 
@@ -1027,7 +956,7 @@ module Sohm
1027
956
  # persisted attributes. All attributes are stored on the Redis
1028
957
  # hash.
1029
958
  #
1030
- # class User < Ohm::Model
959
+ # class User < Sohm::Model
1031
960
  # attribute :name
1032
961
  # end
1033
962
  #
@@ -1042,7 +971,7 @@ module Sohm
1042
971
  # A +lambda+ can be passed as a second parameter to add
1043
972
  # typecasting support to the attribute.
1044
973
  #
1045
- # class User < Ohm::Model
974
+ # class User < Sohm::Model
1046
975
  # attribute :age, ->(x) { x.to_i }
1047
976
  # end
1048
977
  #
@@ -1089,10 +1018,15 @@ module Sohm
1089
1018
 
1090
1019
  if cast
1091
1020
  define_method(name) do
1021
+ # NOTE: This is a temporary solution, since we might use
1022
+ # composite objects (such as arrays), which won't always
1023
+ # do a reset
1024
+ @serial_attributes_changed = true
1092
1025
  cast[@serial_attributes[name]]
1093
1026
  end
1094
1027
  else
1095
1028
  define_method(name) do
1029
+ @serial_attributes_changed = true
1096
1030
  @serial_attributes[name]
1097
1031
  end
1098
1032
  end
@@ -1111,7 +1045,7 @@ module Sohm
1111
1045
  #
1112
1046
  # Example:
1113
1047
  #
1114
- # class User < Ohm::Model
1048
+ # class User < Sohm::Model
1115
1049
  # counter :points
1116
1050
  # end
1117
1051
  #
@@ -1122,7 +1056,7 @@ module Sohm
1122
1056
  # # => 1
1123
1057
  #
1124
1058
  # Note: You can't use counters until you save the model. If you
1125
- # try to do it, you'll receive an Ohm::MissingID error.
1059
+ # try to do it, you'll receive an Sohm::MissingID error.
1126
1060
  #
1127
1061
  def self.counter(name)
1128
1062
  counters << name unless counters.include?(name)
@@ -1130,7 +1064,7 @@ module Sohm
1130
1064
  define_method(name) do
1131
1065
  return 0 if new?
1132
1066
 
1133
- redis.call("HGET", key[:counters], name).to_i
1067
+ redis.call("HGET", key[:_counters], name).to_i
1134
1068
  end
1135
1069
  end
1136
1070
 
@@ -1142,11 +1076,11 @@ module Sohm
1142
1076
  # Create a new model, notice that under Sohm's circumstances,
1143
1077
  # this is no longer a syntactic sugar for Model.new(atts).save
1144
1078
  def self.create(atts = {})
1145
- new(atts).save(create: true)
1079
+ new(atts).save
1146
1080
  end
1147
1081
 
1148
1082
  # Returns the namespace for the keys generated using this model.
1149
- # Check `Ohm::Model.key` documentation for more details.
1083
+ # Check `Sohm::Model.key` documentation for more details.
1150
1084
  def key
1151
1085
  model.key[id]
1152
1086
  end
@@ -1160,7 +1094,6 @@ module Sohm
1160
1094
  def initialize(atts = {})
1161
1095
  @attributes = {}
1162
1096
  @serial_attributes = {}
1163
- @_memo = {}
1164
1097
  @serial_attributes_changed = false
1165
1098
  update_attributes(atts)
1166
1099
  end
@@ -1170,7 +1103,7 @@ module Sohm
1170
1103
  #
1171
1104
  # Example:
1172
1105
  #
1173
- # class User < Ohm::Model; end
1106
+ # class User < Sohm::Model; end
1174
1107
  #
1175
1108
  # u = User.create
1176
1109
  # u.id
@@ -1201,52 +1134,16 @@ module Sohm
1201
1134
  # Preload all the attributes of this model from Redis. Used
1202
1135
  # internally by `Model::[]`.
1203
1136
  def load!
1204
- update_attributes(Utils.dict(redis.call("HGETALL", key))) unless new?
1137
+ update_attributes(Utils.dict(redis.call("HGETALL", key))) if id
1205
1138
  @serial_attributes_changed = false
1206
1139
  return self
1207
1140
  end
1208
1141
 
1209
- # Read an attribute remotely from Redis. Useful if you want to get
1210
- # the most recent value of the attribute and not rely on locally
1211
- # cached value.
1212
- #
1213
- # Example:
1214
- #
1215
- # User.create(:name => "A")
1216
- #
1217
- # Session 1 | Session 2
1218
- # --------------|------------------------
1219
- # u = User[1] | u = User[1]
1220
- # u.name = "B" |
1221
- # u.save |
1222
- # | u.name == "A"
1223
- # | u.get(:name) == "B"
1224
- #
1225
- def get(att)
1226
- @attributes[att] = redis.call("HGET", key, att)
1227
- end
1228
-
1229
- # Update an attribute value atomically. The best usecase for this
1230
- # is when you simply want to update one value.
1231
- #
1232
- # Note: This method is dangerous because it doesn't update indices
1233
- # and uniques. Use it wisely. The safe equivalent is `update`.
1234
- #
1235
- def set(att, val)
1236
- if val.to_s.empty?
1237
- redis.call("HDEL", key, att)
1238
- else
1239
- redis.call("HSET", key, att, val)
1240
- end
1241
-
1242
- @attributes[att] = val
1243
- end
1244
-
1245
1142
  # Returns +true+ if the model is not persisted. Otherwise, returns +false+.
1246
1143
  #
1247
1144
  # Example:
1248
1145
  #
1249
- # class User < Ohm::Model
1146
+ # class User < Sohm::Model
1250
1147
  # attribute :name
1251
1148
  # end
1252
1149
  #
@@ -1258,12 +1155,12 @@ module Sohm
1258
1155
  # u.new?
1259
1156
  # # => false
1260
1157
  def new?
1261
- !model.exists?(id)
1158
+ !(defined?(@id) && model.exists?(id))
1262
1159
  end
1263
1160
 
1264
1161
  # Increment a counter atomically. Internally uses HINCRBY.
1265
1162
  def incr(att, count = 1)
1266
- redis.call("HINCRBY", key[:counters], att, count)
1163
+ redis.call("HINCRBY", key[:_counters], att, count)
1267
1164
  end
1268
1165
 
1269
1166
  # Decrement a counter atomically. Internally uses HINCRBY.
@@ -1294,7 +1191,7 @@ module Sohm
1294
1191
  #
1295
1192
  # Example:
1296
1193
  #
1297
- # class User < Ohm::Model
1194
+ # class User < Sohm::Model
1298
1195
  # attribute :name
1299
1196
  # end
1300
1197
  #
@@ -1316,7 +1213,7 @@ module Sohm
1316
1213
  #
1317
1214
  # Example:
1318
1215
  #
1319
- # class User < Ohm::Model
1216
+ # class User < Sohm::Model
1320
1217
  # attribute :name
1321
1218
  # end
1322
1219
  #
@@ -1326,7 +1223,7 @@ module Sohm
1326
1223
  #
1327
1224
  # In order to add additional attributes, you can override `to_hash`:
1328
1225
  #
1329
- # class User < Ohm::Model
1226
+ # class User < Sohm::Model
1330
1227
  # attribute :name
1331
1228
  #
1332
1229
  # def to_hash
@@ -1351,7 +1248,7 @@ module Sohm
1351
1248
  #
1352
1249
  # Example:
1353
1250
  #
1354
- # class User < Ohm::Model
1251
+ # class User < Sohm::Model
1355
1252
  # attribute :name
1356
1253
  # end
1357
1254
  #
@@ -1362,7 +1259,7 @@ module Sohm
1362
1259
  def save
1363
1260
  if serial_attributes_changed
1364
1261
  response = script(LUA_SAVE, 1, key,
1365
- serial_attributes.to_msgpack,
1262
+ sanitize_attributes(serial_attributes).to_msgpack,
1366
1263
  cas_token)
1367
1264
 
1368
1265
  if response.is_a?(RuntimeError)
@@ -1377,7 +1274,8 @@ module Sohm
1377
1274
  @serial_attributes_changed = false
1378
1275
  end
1379
1276
 
1380
- redis.call("HSET", key, "_ndata", attributes.to_msgpack)
1277
+ redis.call("HSET", key, "_ndata",
1278
+ sanitize_attributes(attributes).to_msgpack)
1381
1279
 
1382
1280
  refresh_indices
1383
1281
 
@@ -1387,18 +1285,21 @@ module Sohm
1387
1285
  # Delete the model, including all the following keys:
1388
1286
  #
1389
1287
  # - <Model>:<id>
1390
- # - <Model>:<id>:counters
1288
+ # - <Model>:<id>:_counters
1391
1289
  # - <Model>:<id>:<set name>
1392
1290
  #
1393
1291
  # If the model has uniques or indices, they're also cleaned up.
1394
1292
  #
1395
1293
  def delete
1396
1294
  memo_key = key["_indices"]
1397
- commands = [["DEL", key], ["DEL", memo_key]]
1295
+ commands = [["DEL", key], ["DEL", memo_key], ["DEL", key["_counters"]]]
1398
1296
  index_list = redis.call("SMEMBERS", memo_key)
1399
1297
  index_list.each do |index_key|
1400
1298
  commands << ["SREM", index_key, id]
1401
1299
  end
1300
+ model.tracked.each do |tracked_key|
1301
+ commands << ["DEL", key[tracked_key]]
1302
+ end
1402
1303
 
1403
1304
  model.synchronize do
1404
1305
  commands.each do |command|
@@ -1459,24 +1360,33 @@ module Sohm
1459
1360
  downcase.to_sym
1460
1361
  end
1461
1362
 
1363
+ # Workaround to JRuby's concurrency problem
1364
+ def self.inherited(subclass)
1365
+ subclass.instance_variable_set(:@indices, [])
1366
+ subclass.instance_variable_set(:@counters, [])
1367
+ subclass.instance_variable_set(:@tracked, [])
1368
+ subclass.instance_variable_set(:@attributes, [])
1369
+ subclass.instance_variable_set(:@serial_attributes, [])
1370
+ end
1371
+
1462
1372
  def self.indices
1463
- @indices ||= []
1373
+ @indices
1464
1374
  end
1465
1375
 
1466
1376
  def self.counters
1467
- @counters ||= []
1377
+ @counters
1468
1378
  end
1469
1379
 
1470
1380
  def self.tracked
1471
- @tracked ||= []
1381
+ @tracked
1472
1382
  end
1473
1383
 
1474
1384
  def self.attributes
1475
- @attributes ||= []
1385
+ @attributes
1476
1386
  end
1477
1387
 
1478
1388
  def self.serial_attributes
1479
- @serial_attributes ||= []
1389
+ @serial_attributes
1480
1390
  end
1481
1391
 
1482
1392
  def self.filters(dict)
@@ -1493,9 +1403,9 @@ module Sohm
1493
1403
  raise IndexNotFound unless indices.include?(att)
1494
1404
 
1495
1405
  if val.kind_of?(Enumerable)
1496
- val.map { |v| key[:indices][att][v] }
1406
+ val.map { |v| key[:_indices][att][v] }
1497
1407
  else
1498
- [key[:indices][att][val]]
1408
+ [key[:_indices][att][val]]
1499
1409
  end
1500
1410
  end
1501
1411
 
@@ -1511,7 +1421,7 @@ module Sohm
1511
1421
  # Add new indices first
1512
1422
  commands = fetch_indices.each_pair.map do |field, vals|
1513
1423
  vals.map do |val|
1514
- index_key = key["_indices"][field][val]
1424
+ index_key = model.key["_indices"][field][val]
1515
1425
  [["SADD", memo_key, index_key], ["SADD", index_key, id]]
1516
1426
  end
1517
1427
  end.flatten(2)
@@ -1529,7 +1439,7 @@ module Sohm
1529
1439
  index_set = ::Set.new(redis.call("SMEMBERS", memo_key))
1530
1440
  valid_list = model[id].send(:fetch_indices).each_pair.map do |field, vals|
1531
1441
  vals.map do |val|
1532
- key["_indices"][field][val]
1442
+ model.key["_indices"][field][val]
1533
1443
  end
1534
1444
  end.flatten(1)
1535
1445
  valid_set = ::Set.new(valid_list)
@@ -1565,6 +1475,10 @@ module Sohm
1565
1475
  attrs
1566
1476
  end
1567
1477
 
1478
+ def sanitize_attributes(attributes)
1479
+ attributes.select { |key, val| val }
1480
+ end
1481
+
1568
1482
  def model
1569
1483
  self.class
1570
1484
  end