blendris 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ module Blendris
2
+
3
+ # RedisNode is used to compose all Redis value wrapper classes.
4
+ module RedisNode
5
+
6
+ include RedisAccessor
7
+
8
+ def initialize(key, options = {})
9
+ @key = sanitize_key(key)
10
+ @default = options[:default]
11
+ @options = options
12
+
13
+ set(@default) if @default && !redis.exists(self.key)
14
+ end
15
+
16
+ def set(value)
17
+ if value
18
+ redis.set key, self.class.cast_to_redis(value, @options)
19
+ else
20
+ redis.del key
21
+ end
22
+ end
23
+
24
+ def get
25
+ self.class.cast_from_redis redis.get(self.key), @options
26
+ end
27
+
28
+ def key
29
+ prefix + @key
30
+ end
31
+
32
+ def clear
33
+ redis.del key
34
+ end
35
+
36
+ def type
37
+ redis.type key
38
+ end
39
+
40
+ def exists?
41
+ redis.exists key
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,51 @@
1
+ module Blendris
2
+
3
+ class RedisReference < RedisReferenceBase
4
+
5
+ include RedisNode
6
+
7
+ def ref
8
+ @ref ||= RedisString.new(@key)
9
+ end
10
+
11
+ def set(obj)
12
+ old_obj = self.get if @reverse
13
+ modified = false
14
+ refkey = self.class.cast_to_redis(obj, @options)
15
+
16
+ if refkey == nil
17
+ ref.set nil
18
+ modified = true
19
+ elsif refkey != ref.get
20
+ ref.set refkey
21
+ apply_reverse_add obj
22
+ modified = true
23
+ end
24
+
25
+ apply_reverse_delete(old_obj) if modified
26
+
27
+ obj
28
+ end
29
+
30
+ def get
31
+ self.class.cast_from_redis ref.get
32
+ end
33
+
34
+ def assign_ref(value)
35
+ self.set value
36
+ end
37
+
38
+ def remove_ref(value)
39
+ self.set nil
40
+ end
41
+
42
+ def references(value)
43
+ refval = ref.get
44
+
45
+ return true if refval.nil? && value.nil?
46
+ return refval == value.key
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,68 @@
1
+ module Blendris
2
+
3
+ class RedisReferenceBase
4
+
5
+ include RedisNode
6
+ extend RedisAccessor
7
+
8
+ def initialize(key, options = {})
9
+ @model = options[:model]
10
+ @key = sanitize_key(key)
11
+ @reverse = options[:reverse]
12
+ @options = options
13
+
14
+ @klass = options[:class] || Model
15
+ @klass = constantize(camelize @klass) if @klass.kind_of? String
16
+
17
+ unless @klass.ancestors.include? Model
18
+ raise ArgumentError.new("#{klass.name} is not a model")
19
+ end
20
+ end
21
+
22
+ def apply_reverse_add(value)
23
+ if @reverse && value
24
+ reverse = value.redis_symbol(@reverse)
25
+ reverse.assign_ref(@model) if !reverse.references @model
26
+ end
27
+ end
28
+
29
+ def apply_reverse_delete(value)
30
+ if @reverse && value
31
+ reverse = value.redis_symbol(@reverse)
32
+ reverse.remove_ref(@model) if reverse.references @model
33
+ end
34
+ end
35
+
36
+ def self.cast_to_redis(obj, options = {})
37
+ expect = options[:class] || Model
38
+ expect = constantize(expect) if expect.kind_of? String
39
+ expect = Model unless expect.ancestors.include? Model
40
+
41
+ if obj == nil
42
+ nil
43
+ elsif obj.kind_of? expect
44
+ obj.key
45
+ else
46
+ raise TypeError.new("#{obj.class.name} is not a #{expect.name}")
47
+ end
48
+ end
49
+
50
+ def self.cast_from_redis(refkey, options = {})
51
+ expect = options[:class] || Model
52
+ expect = constantize(expect) if expect.kind_of? String
53
+ expect = Model unless expect.ancestors.include? Model
54
+
55
+ klass = constantize(redis.get(prefix + refkey)) if refkey
56
+
57
+ if klass == nil
58
+ nil
59
+ elsif klass.ancestors.include? expect
60
+ klass.new refkey
61
+ else
62
+ raise TypeError.new("#{klass.name} is not a #{expect.name}")
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,65 @@
1
+ module Blendris
2
+
3
+ class RedisReferenceSet < RedisReferenceBase
4
+
5
+ include RedisNode
6
+ include Enumerable
7
+
8
+ def refs
9
+ @refs ||= RedisSet.new(@key)
10
+ end
11
+
12
+ def set(*objs)
13
+ objs.flatten!
14
+ objs.compact!
15
+
16
+ objs.each do |obj|
17
+ if refkey = self.class.cast_to_redis(obj, @options)
18
+ refs << refkey
19
+ apply_reverse_add obj
20
+ end
21
+ end
22
+
23
+ self
24
+ end
25
+ alias :<< :set
26
+
27
+ def delete(obj)
28
+ if refkey = self.class.cast_to_redis(obj, @options)
29
+ deleted = refs.delete(refkey)
30
+ apply_reverse_delete(obj) if deleted
31
+ deleted
32
+ end
33
+ end
34
+
35
+ def get
36
+ self
37
+ end
38
+
39
+ def each
40
+ redis.smembers(key).each do |refkey|
41
+ yield self.class.cast_from_redis(refkey, @options)
42
+ end
43
+ end
44
+
45
+ def include?(obj)
46
+ refkey = self.class.cast_to_redis(obj, @options)
47
+
48
+ refs.include? refkey
49
+ end
50
+
51
+ def assign_ref(*values)
52
+ self.set *values
53
+ end
54
+
55
+ def remove_ref(value)
56
+ self.delete value
57
+ end
58
+
59
+ def references(value)
60
+ self.include? value
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,39 @@
1
+ module Blendris
2
+
3
+ class RedisSet
4
+
5
+ include RedisNode
6
+ include Enumerable
7
+
8
+ def initialize(key, options = {})
9
+ @key = key.to_s
10
+ @options = options
11
+ end
12
+
13
+ def each
14
+ redis.smembers(key).each do |value|
15
+ yield value
16
+ end
17
+
18
+ self
19
+ end
20
+
21
+ def <<(value)
22
+ [ value ].flatten.compact.each do |v|
23
+ redis.sadd key, v
24
+ end
25
+
26
+ self
27
+ end
28
+
29
+ def get
30
+ self
31
+ end
32
+
33
+ def delete(value)
34
+ redis.srem key, value
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,19 @@
1
+ module Blendris
2
+
3
+ class RedisString
4
+
5
+ include RedisNode
6
+
7
+ def self.cast_to_redis(value, options = {})
8
+ raise TypeError.new("#{value.class.name} is not a string") unless value.kind_of? String
9
+
10
+ value
11
+ end
12
+
13
+ def self.cast_from_redis(value, options = {})
14
+ value
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,14 @@
1
+ module Blendris
2
+
3
+ class Model
4
+
5
+ type :string, RedisString
6
+ type :integer, RedisInteger
7
+ type :set, RedisSet
8
+ type :list, RedisList
9
+ type :ref, RedisReference
10
+ type :refs, RedisReferenceSet
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,39 @@
1
+ module Blendris
2
+
3
+ module Utils
4
+
5
+ # Method lifted from Rails.
6
+ def constantize(camel_cased_word)
7
+ return if blank(camel_cased_word)
8
+
9
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
10
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
11
+ end
12
+
13
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
14
+ end
15
+
16
+ # Method lifted from Rails.
17
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
18
+ if first_letter_in_uppercase
19
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
20
+ else
21
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
22
+ end
23
+ end
24
+
25
+ # Tests if the given object is blank.
26
+ def blank(obj)
27
+ return true if obj.nil?
28
+ return obj.strip.empty? if obj.kind_of? String
29
+ return obj.empty? if obj.respond_to? :empty?
30
+ return false
31
+ end
32
+
33
+ def sanitize_key(key)
34
+ key.to_s.gsub(/[\r\n\s]/, "_").gsub(/^:+|:+$/, "")
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/blendris.rb'}"
9
+ puts "Loading blendris gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe "redis lists" do
4
+
5
+ it "should read and write" do
6
+ @onion.sales.count.should == 0
7
+
8
+ @onion.sales << %w( to-bill to-tom to-bill to-bob to-tom )
9
+ @onion.sales.count.should == 5
10
+ @onion.sales.to_a.should == %w( to-bill to-tom to-bill to-bob to-tom )
11
+
12
+ @onion.sales.delete("to-bill").should == 2
13
+ @onion.sales.delete("to-bob").should == 1
14
+ @onion.sales.to_a.should == %w( to-tom to-tom )
15
+ end
16
+
17
+ end
@@ -0,0 +1,180 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe Model do
4
+
5
+ it "should have valid keys" do
6
+ @onion.key.should == "food:onion"
7
+ @onion.name.should == "onion"
8
+ end
9
+
10
+ it "should have a working integer field" do
11
+ @onion.calories.should == 0
12
+
13
+ @onion.calories = 120
14
+ @onion.calories.should == 120
15
+ end
16
+
17
+ it "should have a valid reference" do
18
+ @onion.category.should be_nil
19
+
20
+ @onion.category = @vegetable
21
+ @onion.category.name.should == "vegetable"
22
+ end
23
+
24
+ it "should have a valid reference set" do
25
+ @fruit.foods.count.should == 0
26
+
27
+ @fruit.foods << [ @apple, @lemon ]
28
+
29
+ @fruit.foods.should be_include(@apple)
30
+ @fruit.foods.should be_include(@lemon)
31
+ @fruit.foods.should be_include(Food.new("food:lemon"))
32
+ @fruit.foods.should_not be_include(@steak)
33
+ end
34
+
35
+ it "should not allow you to instantiate with a key that doesnt match its class" do
36
+ lambda { Category.new("balogna") }.should raise_error(TypeError)
37
+ lambda { Category.new(@onion.key) }.should raise_error(TypeError)
38
+ lambda { Category.new(@vegetable.key) }.should_not raise_error(TypeError)
39
+ end
40
+
41
+ it "should allow for complex types in the key" do
42
+ lambda { FavoriteFood.create("Billy Bob Thorton", "squeezy") }.should raise_error(TypeError)
43
+
44
+ fav = FavoriteFood.create("Billy Bob Thorton", @onion)
45
+
46
+ fav.key.should == "person:Billy_Bob_Thorton:food:onion"
47
+ fav.person.should == "Billy Bob Thorton"
48
+ fav.food.should == @onion
49
+ end
50
+
51
+ context "with single reference" do
52
+
53
+ it "should reverse to a single reference" do
54
+ @apple.sibling.should be_nil
55
+ @lemon.sibling.should be_nil
56
+
57
+ @apple.sibling = @lemon
58
+
59
+ @apple.sibling.should == @lemon
60
+ @lemon.sibling.should == @apple
61
+
62
+ @lemon.sibling = nil
63
+
64
+ @apple.sibling.should be_nil
65
+ @lemon.sibling.should be_nil
66
+ end
67
+
68
+ it "should reverse to a reference set" do
69
+ @onion.category.should be_nil
70
+ @vegetable.foods.count.should be_zero
71
+
72
+ 2.times do
73
+ @onion.category = @vegetable
74
+
75
+ @onion.category.name.should == "vegetable"
76
+ @vegetable.foods.count.should == 1
77
+ @vegetable.foods.should be_include(@onion)
78
+ end
79
+
80
+ @onion.category = nil
81
+
82
+ @onion.category.should be_nil
83
+ @vegetable.foods.count.should == 0
84
+ @vegetable.foods.should_not be_include(@onion)
85
+ end
86
+
87
+ it "should allow for generic references" do
88
+ @onion.something.should be_nil
89
+
90
+ @onion.something = @steak
91
+ @onion.something.name.should == "steak"
92
+
93
+ @onion.something = @vegetable
94
+ @onion.something.name.should == "vegetable"
95
+ end
96
+
97
+ end
98
+
99
+ context "with reference sets" do
100
+
101
+ it "should reverse to single references" do
102
+ @onion.category.should be_nil
103
+ @vegetable.foods.count.should be_zero
104
+
105
+ 2.times do
106
+ @vegetable.foods << @onion
107
+
108
+ @onion.category.name.should == "vegetable"
109
+ @vegetable.foods.count.should == 1
110
+ @vegetable.foods.should be_include(@onion)
111
+ end
112
+
113
+ @vegetable.foods.delete @onion
114
+
115
+ @onion.category.should be_nil
116
+ @vegetable.foods.count.should == 0
117
+ @vegetable.foods.should_not be_include(@onion)
118
+ end
119
+
120
+ it "should reverse to a reference set" do
121
+ @apple.friends.count.should == 0
122
+ @lemon.friends.count.should == 0
123
+ @onion.friends.count.should == 0
124
+
125
+ @apple.friends << [ @lemon, @onion ]
126
+
127
+ @apple.friends.count.should == 2
128
+ @lemon.friends.count.should == 1
129
+ @onion.friends.count.should == 1
130
+
131
+ @apple.friends.should be_include(@lemon)
132
+ @apple.friends.should be_include(@onion)
133
+ @lemon.friends.should be_include(@apple)
134
+ @onion.friends.should be_include(@apple)
135
+
136
+ @apple.friends.delete @lemon
137
+
138
+ @apple.friends.count.should == 1
139
+ @lemon.friends.count.should == 0
140
+ @onion.friends.count.should == 1
141
+
142
+ @apple.friends.should_not be_include(@lemon)
143
+ @apple.friends.should be_include(@onion)
144
+ @lemon.friends.should_not be_include(@apple)
145
+ @onion.friends.should be_include(@apple)
146
+ end
147
+
148
+ end
149
+
150
+ context "website sister sites" do
151
+
152
+ it "should update each others sister sites table" do
153
+
154
+ site1 = Website.create("Site One")
155
+ site2 = Website.create("Site Two")
156
+
157
+ site1.sister_sites.count.should == 0
158
+ site2.sister_sites.count.should == 0
159
+ site1.sister_sites.should_not be_include site2
160
+ site2.sister_sites.should_not be_include site1
161
+
162
+ site1.sister_sites << site2
163
+
164
+ site1.sister_sites.count.should == 1
165
+ site2.sister_sites.count.should == 1
166
+ site1.sister_sites.should be_include site2
167
+ site2.sister_sites.should be_include site1
168
+
169
+ site2.sister_sites.delete site1
170
+
171
+ site1.sister_sites.count.should == 0
172
+ site2.sister_sites.count.should == 0
173
+ site1.sister_sites.should_not be_include site2
174
+ site2.sister_sites.should_not be_include site1
175
+
176
+ end
177
+
178
+ end
179
+
180
+ end