sohm 0.0.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.gemfile +9 -0
- data/.travis.yml +10 -0
- data/README.md +2 -0
- data/lib/sohm/auto_id.rb +13 -0
- data/lib/sohm/command.rb +1 -1
- data/lib/sohm/index_all.rb +18 -0
- data/lib/sohm/json.rb +1 -1
- data/lib/sohm.rb +121 -207
- data/sohm.gemspec +1 -1
- data/test/association.rb +6 -12
- data/test/command.rb +6 -6
- data/test/connection.rb +5 -5
- data/test/core.rb +4 -1
- data/test/counters.rb +2 -1
- data/test/enumerable.rb +9 -3
- data/test/filtering.rb +10 -13
- data/test/hash_key.rb +3 -1
- data/test/helper.rb +5 -3
- data/test/indices.rb +6 -36
- data/test/json.rb +8 -3
- data/test/list.rb +6 -11
- data/test/model.rb +100 -179
- data/test/set.rb +6 -2
- data/test/sohm.rb +44 -0
- data/test/to_hash.rb +5 -2
- metadata +7 -6
- data/lib/sample.rb +0 -14
- data/lib/sohm/lua/delete.lua +0 -72
- data/test/thread_safety.rb +0 -67
- data/test/uniques.rb +0 -98
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 `
|
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 <
|
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 <
|
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
|
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
|
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 <
|
199
|
+
# class Comment < Sohm::Model
|
199
200
|
# end
|
200
201
|
#
|
201
|
-
# class Post <
|
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 <
|
250
|
+
# class Comment < Sohm::Model
|
250
251
|
# end
|
251
252
|
#
|
252
|
-
# class Post <
|
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 <
|
276
|
+
# class Comment < Sohm::Model
|
276
277
|
# end
|
277
278
|
#
|
278
|
-
# class Post <
|
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
|
-
#
|
381
|
-
#
|
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
|
-
#
|
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
|
-
#
|
386
|
-
#
|
387
|
-
#
|
388
|
-
|
389
|
-
|
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 <
|
348
|
+
# class Post < Sohm::Model
|
405
349
|
# end
|
406
350
|
#
|
407
|
-
# class User <
|
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 <
|
389
|
+
# class Post < Sohm::Model
|
446
390
|
# end
|
447
391
|
#
|
448
|
-
# class User <
|
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?(
|
521
|
+
# User.all.kind_of?(Sohm::Set)
|
587
522
|
# # => true
|
588
523
|
#
|
589
|
-
# User.find(:name => "John").kind_of?(
|
524
|
+
# User.find(:name => "John").kind_of?(Sohm::Set)
|
590
525
|
# # => true
|
591
526
|
#
|
592
|
-
# User.find(:name => "John", :age => 30).kind_of?(
|
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 <
|
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:
|
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
|
-
|
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 <
|
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
|
-
|
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
|
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 <
|
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
|
-
|
798
|
+
Sohm::Set.new(keys.first, key, self)
|
864
799
|
else
|
865
|
-
|
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
|
820
|
+
# Declare an Sohm::Set with the given name.
|
886
821
|
#
|
887
822
|
# Example:
|
888
823
|
#
|
889
|
-
# class User <
|
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
|
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
|
-
|
841
|
+
Sohm::MutableSet.new(key[name], model.key, model)
|
907
842
|
end
|
908
843
|
end
|
909
844
|
|
910
|
-
# Declare an
|
845
|
+
# Declare an Sohm::List with the given name.
|
911
846
|
#
|
912
847
|
# Example:
|
913
848
|
#
|
914
|
-
# class Comment <
|
849
|
+
# class Comment < Sohm::Model
|
915
850
|
# end
|
916
851
|
#
|
917
|
-
# class Post <
|
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
|
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
|
-
|
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 <
|
878
|
+
# class Post < Sohm::Model
|
944
879
|
# reference :user, :User
|
945
880
|
# end
|
946
881
|
#
|
947
|
-
# class User <
|
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 <
|
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 <
|
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 <
|
912
|
+
# class Post < Sohm::Model
|
978
913
|
# attribute :user_id
|
979
914
|
# index :user_id
|
980
915
|
#
|
981
916
|
# def user
|
982
|
-
#
|
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
|
-
|
1020
|
-
|
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 <
|
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 <
|
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 <
|
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
|
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[:
|
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
|
1079
|
+
new(atts).save
|
1146
1080
|
end
|
1147
1081
|
|
1148
1082
|
# Returns the namespace for the keys generated using this model.
|
1149
|
-
# Check `
|
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 <
|
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)))
|
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 <
|
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[:
|
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 <
|
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 <
|
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 <
|
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 <
|
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",
|
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>:
|
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[:
|
1406
|
+
val.map { |v| key[:_indices][att][v] }
|
1497
1407
|
else
|
1498
|
-
[key[:
|
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
|