blendris 0.0.1

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.
@@ -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