redis_object 1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
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