redis_object 1.1 → 1.2

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/README.markdown CHANGED
@@ -3,6 +3,7 @@ RedisObject is a fast and simple-to-use object persistence layer for Ruby.
3
3
 
4
4
  [![Build Status](https://travis-ci.org/remotezygote/RedisObject.png?branch=master)](https://travis-ci.org/remotezygote/RedisObject)
5
5
  [![Coverage Status](https://coveralls.io/repos/remotezygote/RedisObject/badge.png?branch=master)](https://coveralls.io/r/remotezygote/RedisObject?branch=master)
6
+ [![Code Climate](https://codeclimate.com/github/remotezygote/RedisObject.png)](https://codeclimate.com/github/remotezygote/RedisObject)
6
7
 
7
8
  ## Prerequisites
8
9
  You'll need [Redis](http://redis.io). Other storage adapters are in the works. Maybe.
@@ -72,7 +72,9 @@ module Seabright
72
72
 
73
73
  def get(k)
74
74
  cached_hash_values[k.to_s] ||= Proc.new {|key|
75
- if v = store.hget(hkey, key.to_s)
75
+ if is_ref_key?(k) && (v = get_reference(store.hget(hkey, key.to_s)))
76
+ define_setter_getter(key)
77
+ elsif v = store.hget(hkey, key.to_s)
76
78
  define_setter_getter(key)
77
79
  end
78
80
  v
@@ -111,12 +113,40 @@ module Seabright
111
113
  end
112
114
 
113
115
  def set(k,v)
116
+ return set_ref(k,v) if v.is_a?(RedisObject)
114
117
  store.hset(hkey, k.to_s, v.to_s)
115
118
  cached_hash_values[k.to_s] = v
116
119
  define_setter_getter(k)
117
120
  v
118
121
  end
119
122
 
123
+ def set_ref(k,v)
124
+ return unless v.is_a?(RedisObject)
125
+ track_ref_key(k)
126
+ store.hset(hkey, k.to_s, v.hkey)
127
+ cached_hash_values[k.to_s] = v
128
+ define_setter_getter(k)
129
+ v
130
+ end
131
+
132
+ def track_ref_key(k)
133
+ store.sadd(ref_field_key, k.to_s)
134
+ end
135
+
136
+ def is_ref_key?(k)
137
+ if store.sismember(ref_field_key,k.to_s)
138
+ return true
139
+ end
140
+ false
141
+ end
142
+
143
+ def get_reference(hkey)
144
+ if o = RedisObject.find_by_key(hkey)
145
+ return o
146
+ end
147
+ nil
148
+ end
149
+
120
150
  def setnx(k,v)
121
151
  if success = store.hsetnx(hkey, k.to_s, v.to_s)
122
152
  cached_hash_values[k.to_s] = v
@@ -60,7 +60,7 @@ module Seabright
60
60
  end
61
61
 
62
62
  def untrack_script(name)
63
- ScriptSHAMap.delete name
63
+ $ScriptSHAMap.delete name
64
64
  (@tmp_store || store).del(script_sha_key(name))
65
65
  end
66
66
 
@@ -75,6 +75,7 @@ module Seabright
75
75
  store_obj.keys(script_sha_key("*")).each do |k|
76
76
  store_obj.del k
77
77
  end
78
+ $ScriptSHAMap = {}
78
79
  end
79
80
 
80
81
  def script_sha_key(name)
@@ -11,40 +11,32 @@ module Seabright
11
11
  def intercept_sets_for_triggers!
12
12
  return if @intercepted_sets_for_triggers
13
13
  self.class_eval do
14
- alias_method :untriggered_set, :set unless method_defined?(:untriggered_set)
15
- def set(k,v)
16
- untriggered_set(k,v)
17
- unless self.class.untriggerables.include?(k)
18
- begin
19
- self.class.untriggerables << k
20
- if self.class.field_triggers[k.to_sym]
21
- send(self.class.field_triggers[k.to_sym],k,v)
22
- end
23
- self.class.update_triggers.each do |actn|
24
- send(actn.to_sym,k,v)
25
- end
26
- ensure
27
- self.class.untriggerables.delete k
14
+
15
+ def triggered_set(cmd,k,v)
16
+ ret = send("untriggered_#{cmd}".to_sym,k,v)
17
+ return ret if self.class.untriggerables.include?(k)
18
+ begin
19
+ self.class.untriggerables << k
20
+ if self.class.field_triggers[k.to_sym]
21
+ send(self.class.field_triggers[k.to_sym],k,v)
22
+ end
23
+ self.class.update_triggers.each do |actn|
24
+ send(actn.to_sym,k,v)
28
25
  end
26
+ ensure
27
+ self.class.untriggerables.delete k
29
28
  end
29
+ ret
30
30
  end
31
+
32
+ alias_method :untriggered_set, :set unless method_defined?(:untriggered_set)
33
+ def set(k,v)
34
+ triggered_set(:set,k,v)
35
+ end
36
+
31
37
  alias_method :untriggered_setnx, :setnx unless method_defined?(:untriggered_setnx)
32
38
  def setnx(k,v)
33
- ret = untriggered_setnx(k,v)
34
- unless self.class.untriggerables.include?(k)
35
- begin
36
- self.class.untriggerables << k
37
- if self.class.field_triggers[k.to_sym]
38
- send(self.class.field_triggers[k.to_sym],k,v)
39
- end
40
- self.class.update_triggers.each do |actn|
41
- send(actn.to_sym,k,v)
42
- end
43
- ensure
44
- self.class.untriggerables.delete k
45
- end
46
- end
47
- ret
39
+ triggered_set(:setnx,k,v)
48
40
  end
49
41
 
50
42
  end
@@ -13,6 +13,10 @@ module Seabright
13
13
  "#{key}_h"
14
14
  end
15
15
 
16
+ def ref_field_key(ident = nil)
17
+ "#{key}_ref_fields"
18
+ end
19
+
16
20
  module ClassMethods
17
21
 
18
22
  def key(ident=nil)
@@ -27,6 +31,10 @@ module Seabright
27
31
  "#{key(ident)}_h"
28
32
  end
29
33
 
34
+ def ref_field_key(ident = nil)
35
+ "#{key(ident)}_ref_fields"
36
+ end
37
+
30
38
  end
31
39
 
32
40
  def self.included(base)
@@ -1,127 +1,87 @@
1
1
  module Seabright
2
2
  module Types
3
3
 
4
- def enforce_format(k,v)
5
- if v && fmt = self.class.field_formats[k.to_sym]
6
- send(fmt,v)
7
- else
8
- v
9
- end
10
- end
11
-
12
- def score_format(k,v)
13
- if v && fmt = self.class.score_formats[k.to_sym]
14
- send(fmt,v)
15
- else
16
- 0
17
- end
18
- end
19
-
20
- def save_format(k,v)
21
- v && (fmt = self.class.save_formats[k.to_s.gsub(/\=$/,'').to_sym]) ? send(fmt,v) : v
22
- end
23
-
24
- def format_date(val)
25
- begin
26
- val.is_a?(DateTime) || val.is_a?(Date) || val.is_a?(Time) ? val : ( val.is_a?(String) ? DateTime.parse(val) : nil )
27
- rescue StandardError => e
28
- puts "Could not parse value as date using Date.parse. Returning nil instead. Value: #{val.inspect}\nError: #{e.inspect}" if DEBUG
29
- nil
30
- end
31
- end
32
-
33
- def score_date(val)
34
- val.to_time.to_i
35
- end
36
-
37
- def format_array(val)
38
- Yajl::Parser.new(:symbolize_keys => true).parse(val)
39
- end
40
-
41
- def save_array(val)
42
- Yajl::Encoder.encode(val)
4
+ def self.alias_type(als,sym)
5
+ type_aliases[als.to_s.downcase.to_sym] = sym.to_s.downcase.to_sym
43
6
  end
44
7
 
45
- def format_number(val)
46
- val.to_i
8
+ def self.type_aliases
9
+ @type_aliases ||= {}
47
10
  end
48
11
 
49
- def score_number(val)
50
- Float(val)
12
+ def self.register_type(sym)
13
+ known_types << sym.to_s.downcase.to_sym
51
14
  end
52
15
 
53
- def format_float(val)
54
- Float(val)
16
+ def self.known_types
17
+ @known_types ||= []
55
18
  end
56
- alias_method :score_float, :format_float
57
19
 
58
- def format_json(val)
59
- Yajl::Parser.new(:symbolize_keys => true).parse(val)
60
- end
61
-
62
- def save_json(val)
63
- Yajl::Encoder.encode(val)
20
+ def type_filter_for(prefix,k)
21
+ if (fmt = self.class.field_formats[k.to_sym]) && (sym = "#{prefix}_#{fmt}".to_sym) && respond_to?(sym)
22
+ return sym
23
+ end
24
+ nil
64
25
  end
65
26
 
66
- def format_boolean(val)
67
- val=="true"
27
+ def enforce_format(k,v)
28
+ if sym = type_filter_for(:format,k)
29
+ return send(sym,v)
30
+ end
31
+ v
68
32
  end
69
33
 
70
- def save_boolean(val)
71
- val === true ? "true" : "false"
34
+ def score_format(k,v)
35
+ if sym = type_filter_for(:score,k)
36
+ return send(sym,v)
37
+ end
38
+ 0
72
39
  end
73
40
 
74
- def score_boolean(val)
75
- val ? 1 : 0
41
+ def save_format(k,v)
42
+ if sym = type_filter_for(:save,k)
43
+ return send(sym,v)
44
+ end
45
+ v
76
46
  end
77
47
 
78
48
  module ClassMethods
79
49
 
80
- def date(k)
81
- set_field_format(k, :format_date)
82
- set_score_format(k, :score_date)
83
- end
84
-
85
- def number(k)
86
- set_field_format(k, :format_number)
87
- set_score_format(k, :score_number)
88
- end
89
- alias_method :int, :number
90
-
91
- def float(k)
92
- set_field_format(k, :format_float)
93
- set_score_format(k, :score_float)
94
- end
95
-
96
- def bool(k)
97
- set_field_format(k, :format_boolean)
98
- set_score_format(k, :score_boolean)
99
- set_save_format(k, :save_boolean)
100
- end
101
- alias_method :boolean, :bool
102
-
103
- def array(k)
104
- set_field_format(k, :format_array)
105
- set_save_format(k, :save_array)
50
+ def method_missing(sym,*args,&block)
51
+ if als = Types.type_aliases[sym]
52
+ org = sym
53
+ sym = als
54
+ end
55
+ if Types.known_types.include?(sym)
56
+ register_type(sym,org)
57
+ send(sym,*args,&block)
58
+ else
59
+ super(sym,*args,&block)
60
+ end
106
61
  end
107
62
 
108
- def json(k)
109
- set_field_format(k, :format_json)
110
- set_save_format(k, :save_json)
63
+ def register_type(sym,als=nil)
64
+ sym = sym.to_sym
65
+ return if self.respond_to?(sym)
66
+ self.send(:include,Types.const_get("#{sym.to_s.capitalize}Type".to_sym))
67
+ metaclass = class << self; self; end
68
+ metaclass.class_eval do
69
+ define_method(sym) do |k|
70
+ set_field_format k, sym
71
+ end
72
+ if als
73
+ als = als.to_sym
74
+ define_method(als) do |k|
75
+ set_field_format k, sym
76
+ end
77
+ end
78
+ end
111
79
  end
112
80
 
113
81
  def field_formats
114
82
  @field_formats_hash ||= (defined?(superclass.field_formats) ? superclass.field_formats.clone : {})
115
83
  end
116
84
 
117
- def score_formats
118
- @score_formats_hash ||= (defined?(superclass.score_formats) ? superclass.score_formats.clone : {})
119
- end
120
-
121
- def save_formats
122
- @save_formats_hash ||= (defined?(superclass.save_formats) ? superclass.save_formats.clone : {})
123
- end
124
-
125
85
  def set_field_format(k, v)
126
86
  field_formats_set_locally.add(k)
127
87
  field_formats[k] = v
@@ -147,56 +107,6 @@ module Seabright
147
107
  end
148
108
  end
149
109
 
150
- def set_score_format(k, v)
151
- score_formats_set_locally.add(k)
152
- score_formats[k] = v
153
- update_child_class_score_formats(k, v)
154
- intercept_for_typing!
155
- end
156
-
157
- def score_formats_set_locally
158
- @score_formats_set_locally_set ||= Set.new
159
- end
160
-
161
- def inherit_score_format(k, v)
162
- unless scores_formats_set_locally.include? k
163
- score_formats[k] = v
164
- update_child_class_score_formats(k, v)
165
- end
166
- intercept_for_typing!
167
- end
168
-
169
- def update_child_class_score_formats(k, v)
170
- child_classes.each do |child_class|
171
- child_class.inherit_score_format(k, v)
172
- end
173
- end
174
-
175
- def set_save_format(k, v)
176
- save_formats_set_locally.add(k)
177
- save_formats[k] = v
178
- update_child_class_save_formats(k, v)
179
- intercept_for_typing!
180
- end
181
-
182
- def save_formats_set_locally
183
- @save_formats_set_locally_set ||= Set.new
184
- end
185
-
186
- def inherit_save_format(k, v)
187
- unless save_formats_set_locally.include? k
188
- save_formats[k] = v
189
- update_child_class_save_formats(k, v)
190
- end
191
- intercept_for_typing!
192
- end
193
-
194
- def update_child_class_save_formats(k, v)
195
- child_classes.each do |child_class|
196
- child_class.inherit_save_format(k, v)
197
- end
198
- end
199
-
200
110
  def register_format(k,fmt)
201
111
  send(fmt, k)
202
112
  end
@@ -274,3 +184,9 @@ module Seabright
274
184
 
275
185
  end
276
186
  end
187
+ require "redis_object/types/array"
188
+ require "redis_object/types/boolean"
189
+ require "redis_object/types/date"
190
+ require "redis_object/types/float"
191
+ require "redis_object/types/json"
192
+ require "redis_object/types/number"
@@ -0,0 +1,18 @@
1
+ module Seabright
2
+ module Types
3
+ module ArrayType
4
+
5
+ def format_array(val)
6
+ Yajl::Parser.new(:symbolize_keys => true).parse(val)
7
+ end
8
+
9
+ def save_array(val)
10
+ Yajl::Encoder.encode(val)
11
+ end
12
+
13
+ end
14
+
15
+ register_type :Array
16
+
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ module Seabright
2
+ module Types
3
+ module BooleanType
4
+
5
+ def format_boolean(val)
6
+ val=="true"
7
+ end
8
+
9
+ def save_boolean(val)
10
+ val === true ? "true" : "false"
11
+ end
12
+
13
+ def score_boolean(val)
14
+ val ? 1 : 0
15
+ end
16
+
17
+ end
18
+
19
+ register_type :Boolean
20
+ alias_type :Bool, :Boolean
21
+
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Seabright
2
+ module Types
3
+ module DateType
4
+
5
+ def format_date(val)
6
+ begin
7
+ val.is_a?(DateTime) || val.is_a?(Date) || val.is_a?(Time) ? val : ( val.is_a?(String) ? DateTime.parse(val) : nil )
8
+ rescue StandardError => e
9
+ puts "Could not parse value as date using Date.parse. Returning nil instead. Value: #{val.inspect}\nError: #{e.inspect}" if DEBUG
10
+ nil
11
+ end
12
+ end
13
+
14
+ def score_date(val)
15
+ val.to_time.to_i
16
+ end
17
+
18
+ end
19
+
20
+ register_type :Date
21
+
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Seabright
2
+ module Types
3
+ module FloatType
4
+
5
+ def format_float(val)
6
+ Float(val)
7
+ end
8
+ alias_method :score_float, :format_float
9
+
10
+ end
11
+
12
+ register_type :Float
13
+
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Seabright
2
+ module Types
3
+ module JsonType
4
+
5
+ def format_json(val)
6
+ Yajl::Parser.new(:symbolize_keys => true).parse(val)
7
+ end
8
+
9
+ def save_json(val)
10
+ Yajl::Encoder.encode(val)
11
+ end
12
+
13
+ end
14
+
15
+ register_type :Json
16
+
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module Seabright
2
+ module Types
3
+ module NumberType
4
+
5
+ def format_number(val)
6
+ val.to_i
7
+ end
8
+
9
+ def score_number(val)
10
+ Float(val)
11
+ end
12
+
13
+ end
14
+
15
+ register_type :Number
16
+ alias_type :Int, :Number
17
+
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  module Seabright
2
2
  class RedisObject
3
- VERSION = "1.1"
3
+ VERSION = "1.2"
4
4
  end
5
5
  end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ module FieldRefSpec
4
+
5
+ class Dad < RedisObject; end
6
+ class Son < RedisObject; end
7
+
8
+ describe RedisObject do
9
+
10
+ before do
11
+ RedisObject.store.flushdb
12
+ @dad = Dad.create("daddy")
13
+ @son = Son.create("sonny")
14
+ @dad.stepson = @son
15
+ end
16
+
17
+ it "can store an object at any field location" do
18
+
19
+ @dad.stepson = @son
20
+
21
+ end
22
+
23
+ it "can get the object back by get" do
24
+
25
+ @dad.get(:stepson).should be_a(Son)
26
+
27
+ end
28
+
29
+ it "can get the object back by bracket" do
30
+
31
+ @dad[:stepson].should be_a(Son)
32
+
33
+ end
34
+
35
+ it "can get the object back by pseudo-getter" do
36
+
37
+ @dad.stepson.should be_a(Son)
38
+
39
+ end
40
+
41
+ it "can get the object back after reload" do
42
+
43
+ Dad.find(@dad.id).stepson.should be_a(Son)
44
+
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,62 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ module ScriptCacheSpec
4
+
5
+ class GenericObject < RedisObject;end
6
+
7
+ describe Seabright::CachedScripts do
8
+ before do
9
+
10
+ RedisObject.store.flushdb
11
+
12
+ (1..5).each do |n|
13
+ GenericObject.create(n.to_s)
14
+ end
15
+
16
+ end
17
+
18
+ it "should cache scripts" do
19
+
20
+ GenericObject.recently_created.first.id.should eq("5")
21
+
22
+ end
23
+
24
+ it "should untrack a script" do
25
+
26
+ GenericObject.recently_created.first.id.should eq("5")
27
+ GenericObject.indexed(:created_at,-1,false).to_a.last.id.should eq("4")
28
+
29
+ $ScriptSHAMap.keys.count.should eq(1)
30
+ RedisObject.untrack_script :RevScript
31
+ $ScriptSHAMap.keys.count.should eq(0)
32
+
33
+ end
34
+
35
+ it "should handle a missing script SHA" do
36
+
37
+ GenericObject.recently_created.first.id.should eq("5")
38
+ RedisObject.store.script :flush
39
+ GenericObject.recently_created.to_a[2].id.should eq("3")
40
+
41
+ end
42
+
43
+ it "should expire scripts" do
44
+
45
+ # $ScriptSHAMap.keys.count.should eq(1)
46
+
47
+ RedisObject.stores.each do |(name,store)|
48
+ RedisObject.expire_all_script_shas(store)
49
+ end
50
+
51
+ $ScriptSHAMap.keys.count.should eq(0)
52
+
53
+ end
54
+
55
+ it "should error on unknown script source" do
56
+
57
+ expect { GenericObject.run_script(:MysteriousCommand) }.to raise_error
58
+
59
+ end
60
+
61
+ end
62
+ end
@@ -4,10 +4,6 @@ module TriggerSpec
4
4
 
5
5
  class TimestampedObject < RedisObject;end
6
6
 
7
- class UnTimestampedObject < RedisObject
8
- # time_matters_not!
9
- end
10
-
11
7
  describe Seabright::Triggers do
12
8
  before do
13
9
  RedisObject.store.flushdb
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_object
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: '1.2'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -113,6 +113,12 @@ files:
113
113
  - lib/redis_object/timestamps.rb
114
114
  - lib/redis_object/tpl.rb
115
115
  - lib/redis_object/types.rb
116
+ - lib/redis_object/types/array.rb
117
+ - lib/redis_object/types/boolean.rb
118
+ - lib/redis_object/types/date.rb
119
+ - lib/redis_object/types/float.rb
120
+ - lib/redis_object/types/json.rb
121
+ - lib/redis_object/types/number.rb
116
122
  - lib/redis_object/validation.rb
117
123
  - lib/redis_object/version.rb
118
124
  - redis_object.gemspec
@@ -121,9 +127,11 @@ files:
121
127
  - spec/benchmark_spec.rb
122
128
  - spec/collections_spec.rb
123
129
  - spec/defaults_spec.rb
130
+ - spec/field_ref_spec.rb
124
131
  - spec/filters_spec.rb
125
132
  - spec/indices_spec.rb
126
133
  - spec/rename_class_spec.rb
134
+ - spec/script_cache_spec.rb
127
135
  - spec/spec_helper.rb
128
136
  - spec/timestamp_spec.rb
129
137
  - spec/trigger_spec.rb
@@ -161,9 +169,11 @@ test_files:
161
169
  - spec/benchmark_spec.rb
162
170
  - spec/collections_spec.rb
163
171
  - spec/defaults_spec.rb
172
+ - spec/field_ref_spec.rb
164
173
  - spec/filters_spec.rb
165
174
  - spec/indices_spec.rb
166
175
  - spec/rename_class_spec.rb
176
+ - spec/script_cache_spec.rb
167
177
  - spec/spec_helper.rb
168
178
  - spec/timestamp_spec.rb
169
179
  - spec/trigger_spec.rb