risky 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ class Indexed < Risky
4
+ include Risky::ListKeys
5
+ include Risky::Indexes
6
+
7
+ bucket :risky_indexes
8
+ value :value
9
+ value :unique
10
+ index :value
11
+ index :unique, :unique => true
12
+ end
13
+
14
+
15
+ describe 'indexes' do
16
+ before :all do
17
+ Indexed.delete_all
18
+ end
19
+
20
+ it 'can index a string' do
21
+ o = Indexed.new 'test', 'value' => 'value'
22
+ o.save.should_not be_false
23
+ Indexed.by_value('value').should === o
24
+ end
25
+
26
+ it 'can keep values unique (mostly)' do
27
+ o = Indexed.new '1', 'unique' => 'u'
28
+ o.save.should_not be_false
29
+
30
+ o2 = Indexed.new '2', 'unique' => 'u'
31
+ o2.save.should be_false
32
+ o2.errors[:unique].should == 'taken'
33
+ end
34
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+ require 'risky/resolver'
3
+
4
+ Thread.abort_on_exception = true
5
+
6
+ class Multi < Risky
7
+ include Risky::ListKeys
8
+ include Risky::Resolver
9
+
10
+ bucket :risky_mult
11
+ allow_mult
12
+ value :users, :default => []
13
+ value :union, :resolve => :union
14
+ value :intersection, :resolve => Risky::Resolver::Resolvers.method(:intersection)
15
+ value :max, :resolve => :max
16
+ value :min, :resolve => :min
17
+ value :merge, :resolve => :merge
18
+ value :custom, :resolve => lambda { |xs|
19
+ :custom
20
+ }
21
+
22
+ def self.merge(v)
23
+ p = super v
24
+
25
+ p.users = v.map(&:users).min
26
+
27
+ p
28
+ end
29
+ end
30
+
31
+ def test(property, ins, out)
32
+ it property do
33
+ conflict(Multi, property, ins)[property].should == out
34
+ end
35
+ end
36
+
37
+ def set_test(property, ins, out)
38
+ it property do
39
+ conflict(Multi, property, ins)[property].to_set.should == out.to_set
40
+ end
41
+ end
42
+
43
+
44
+ describe Risky::Resolver do
45
+ set_test 'union', [[1], [2]], [1,2]
46
+ set_test 'union', [[1], nil], [1]
47
+ set_test 'union', [[1,4,1], [2,3], [4,4]], [1,2,3,4]
48
+ set_test 'intersection', [[1,2],[]], []
49
+ set_test 'intersection', [[1,2,3,4], [1,2,3], [2,3,4]], [2,3]
50
+ test 'min', [0,1,2,3], 0
51
+ test 'max', [0,2,4,2], 4
52
+ test 'max', [nil, nil], nil
53
+ test 'max', [nil, 4], 4
54
+ test 'custom', ['a', 'b', 'c'], :custom
55
+ end
@@ -0,0 +1,222 @@
1
+ require 'spec_helper'
2
+
3
+ class Album < Risky
4
+ include Risky::ListKeys
5
+ include Risky::SecondaryIndexes
6
+
7
+ bucket :risky_albums
8
+ allow_mult
9
+ index2i :artist_id, :map => true
10
+ index2i :label_key, :map => '_key', :finder => :find_by_id, :allow_nil => true
11
+ index2i :genre, :type => :bin, :allow_nil => true
12
+ index2i :tags, :type => :bin, :multi => true, :allow_nil => true
13
+ value :name
14
+ value :year
15
+ end
16
+
17
+ class Artist < Risky
18
+ include Risky::ListKeys
19
+
20
+ bucket :risky_artists
21
+ value :name
22
+ end
23
+
24
+ class Label < Risky
25
+ include Risky::ListKeys
26
+
27
+ bucket :risky_labels
28
+ value :name
29
+
30
+ def self.find_by_id(id)
31
+ find(id)
32
+ end
33
+ end
34
+
35
+ class City < Risky
36
+ include Risky::SecondaryIndexes
37
+
38
+ bucket :risky_cities
39
+ index2i :country_id, :type => :invalid, :allow_nil => true
40
+ value :name
41
+ value :details
42
+ end
43
+
44
+
45
+ describe Risky::SecondaryIndexes do
46
+ let(:artist) { Artist.create(1, :name => 'Motorhead') }
47
+ let(:label) { Label.create(1, :name => 'Bronze Records') }
48
+
49
+ before :each do
50
+ Album.delete_all
51
+ Artist.delete_all
52
+ Label.delete_all
53
+ end
54
+
55
+ it "sets indexes on initialize" do
56
+ album = Album.new(1, {:name => 'Bomber', :year => 1979}, {:artist_id => 2})
57
+ album.indexes2i.should == {"artist_id" => 2 }
58
+ end
59
+
60
+ it "defines getter and setter methods" do
61
+ album = Album.new(1)
62
+ album.artist_id = 1
63
+ album.artist_id.should == 1
64
+ end
65
+
66
+ it "defines association getter and setter methods" do
67
+ album = Album.new(1)
68
+ album.artist = artist
69
+ album.artist.should == artist
70
+ end
71
+
72
+ it "defines association getter and setter methods when using suffix" do
73
+ album = Album.new(1)
74
+ album.label = label
75
+ album.label.should == label
76
+ end
77
+
78
+ it "can use a custom finder" do
79
+ album = Album.create(1, {:name => 'Bomber', :year => 1979},
80
+ {:artist_id => artist.id, :label_key => label.id})
81
+
82
+ Label.should_receive(:find_by_id).with(label.id).and_return(label)
83
+
84
+ album.label.should == label
85
+ end
86
+
87
+ it "resets association if associated object is not saved" do
88
+ artist = Artist.new('new_key')
89
+ album = Album.new('new_key')
90
+ album.artist = artist
91
+ album.artist.should be_nil
92
+ end
93
+
94
+ it "assigns attributs after association assignment" do
95
+ album = Album.new(1)
96
+ album.artist = artist
97
+ album.artist_id.should == artist.id
98
+ end
99
+
100
+ it "assigns association after attribute assignment" do
101
+ album = Album.new(1)
102
+ album.artist_id = artist.id
103
+ album.artist.should == artist
104
+ end
105
+
106
+ it "saves a model with indexes" do
107
+ album = Album.new(1, {:name => 'Ace of Spades' }, { :artist_id => 1 }).save
108
+ album.artist_id.should == 1
109
+ end
110
+
111
+ it "creates a model with indexes" do
112
+ album = Album.create(1, {:name => 'Ace of Spades' }, { :artist_id => 1 })
113
+ album.artist_id.should == 1
114
+ end
115
+
116
+ it "persists association after save" do
117
+ album = Album.new('persist_key')
118
+ album.name = 'Ace of Spades'
119
+ album.artist_id = artist.id
120
+ album.save
121
+
122
+ album.artist.should == artist
123
+ album.artist_id.should == artist.id
124
+
125
+ album.reload
126
+
127
+ album.artist.should == artist
128
+ album.artist_id.should == artist.id
129
+
130
+ album = Album.find(album.key)
131
+
132
+ album.artist.should == artist
133
+ album.artist_id.should == artist.id
134
+ end
135
+
136
+ it "finds first by int secondary index" do
137
+ album = Album.create(1, {:name => 'Bomber', :year => 1979},
138
+ {:artist_id => artist.id})
139
+
140
+ albums = Album.find_by_index(:artist_id, artist.id)
141
+ albums.should == album
142
+ end
143
+
144
+ it "finds all by int secondary index" do
145
+ album1 = Album.create(1, {:name => 'Bomber', :year => 1979},
146
+ {:artist_id => artist.id, :label_key => label.id})
147
+ album2 = Album.create(2, {:name => 'Ace Of Spaces', :year => 1980},
148
+ {:artist_id => artist.id, :label_key => label.id})
149
+
150
+ albums = Album.find_all_by_index(:artist_id, artist.id)
151
+ albums.should include(album1)
152
+ albums.should include(album2)
153
+ end
154
+
155
+ it "finds all by binary secondary index" do
156
+ album = Album.create(1, {:name => 'Bomber', :year => 1979},
157
+ {:artist_id => artist.id, :label_key => label.id, :genre => 'heavy'})
158
+
159
+ Album.find_all_by_index(:genre, 'heavy').should == [album]
160
+ end
161
+
162
+ it "finds all by multi binary secondary index" do
163
+ album = Album.create(1, {:name => 'Bomber', :year => 1979},
164
+ {:artist_id => artist.id, :label_key => label.id,
165
+ :tags => ['rock', 'heavy']})
166
+
167
+ Album.find_all_by_index(:tags, 'heavy').should == [album]
168
+ Album.find_all_by_index(:tags, 'rock').should == [album]
169
+ end
170
+
171
+ it "paginates keys" do
172
+ album1 = Album.create('1', {:name => 'Bomber', :year => 1979},
173
+ {:artist_id => artist.id, :label_key => label.id})
174
+ album2 = Album.create('2', {:name => 'Ace Of Spaces', :year => 1980},
175
+ {:artist_id => artist.id, :label_key => label.id})
176
+ album3 = Album.create('3', {:name => 'Overkill', :year => 1979},
177
+ {:artist_id => artist.id, :label_key => label.id})
178
+
179
+ page1 = Album.paginate_keys_by_index(:artist_id, artist.id, :max_results => 2)
180
+ page1.should == ['1', '2']
181
+ page1.continuation.should_not be_blank
182
+
183
+ page2 = Album.paginate_keys_by_index(:artist_id, artist.id, :max_results => 2, :continuation => page1.continuation)
184
+ page2.should == ['3']
185
+ page2.continuation.should be_blank
186
+ end
187
+
188
+ it "paginates risky objects" do
189
+ album1 = Album.create('1', {:name => 'Bomber', :year => 1979},
190
+ {:artist_id => artist.id, :label_key => label.id})
191
+ album2 = Album.create('2', {:name => 'Ace Of Spaces', :year => 1980},
192
+ {:artist_id => artist.id, :label_key => label.id})
193
+ album3 = Album.create('3', {:name => 'Overkill', :year => 1979},
194
+ {:artist_id => artist.id, :label_key => label.id})
195
+
196
+ page1 = Album.paginate_by_index(:artist_id, artist.id, :max_results => 2)
197
+ page1.should == [album1, album2]
198
+ page1.continuation.should_not be_blank
199
+
200
+ page2 = Album.paginate_by_index(:artist_id, artist.id, :max_results => 2, :continuation => page1.continuation)
201
+ page2.should == [album3]
202
+ page2.continuation.should be_blank
203
+ end
204
+
205
+ it "raises an exception when index is nil" do
206
+ album = Album.new(1)
207
+ expect { album.save }.to raise_error(ArgumentError)
208
+ end
209
+
210
+ it "raises an exception when type is invalid" do
211
+ city = City.new(1)
212
+ expect { city.save }.to raise_error(TypeError)
213
+ end
214
+
215
+ it "can inspect a model" do
216
+ album = Album.new(1, { :name => 'Bomber' }, { :artist_id => 2 })
217
+
218
+ album.inspect.should match(/Album 1/)
219
+ album.inspect.should match(/"name"=>"Bomber"/)
220
+ album.inspect.should match(/"artist_id"=>2/)
221
+ end
222
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ class Crud < Risky
4
+ include Risky::ListKeys
5
+
6
+ bucket :risky_crud
7
+ value :value
8
+ end
9
+
10
+ class Concurrent < Risky
11
+ include Risky::ListKeys
12
+
13
+ bucket :risky_concurrent
14
+ allow_mult
15
+ value :v
16
+
17
+ # Merge value v together as a list
18
+ def self.merge(versions)
19
+ p = super versions
20
+ p.v = versions.inject([]) do |merged, version|
21
+ merged + [*version.v]
22
+ end.uniq
23
+ p
24
+ end
25
+ end
26
+
27
+
28
+ describe 'Threads' do
29
+ it 'supports concurrent modification' do
30
+ Concurrent.bucket.props['allow_mult'].should be_true
31
+
32
+ # Riak doesn't do well with concurrent *new* writes, so get an existing
33
+ # value in there first.
34
+ c = Concurrent.get_or_new('c')
35
+ c.v = []
36
+ c.save(:w => :all)
37
+
38
+ workers = 10
39
+
40
+ # Make a bunch of concurrent writes
41
+ (0...workers).map do |i|
42
+ Thread.new do
43
+ # Give them a little bit of jitter, just to make the vclocks interesting
44
+ sleep rand/6
45
+ c = Concurrent.get_or_new('c')
46
+ c.v << i
47
+ c.save or raise
48
+ end
49
+ end.each do |thread|
50
+ thread.join
51
+ end
52
+
53
+ # Check to ensure we obsoleted or have an extant write for every thread.
54
+ final = Concurrent['c', {:r => :all}]
55
+ final.v.compact.sort.should == (0...workers).to_a
56
+ end
57
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ class User < Risky
4
+ include Risky::ListKeys
5
+
6
+ bucket 'risky_users'
7
+ allow_mult
8
+ value :admin, :default => false
9
+ value :age
10
+ end
11
+
12
+ describe 'Risky' do
13
+ before :each do
14
+ User.delete_all
15
+ end
16
+
17
+ it 'has a bucket' do
18
+ User.bucket.should be_kind_of Riak::Bucket
19
+ end
20
+
21
+ it "can store a value and retrieve it" do
22
+ user = User.new('test', 'admin' => true)
23
+ user.save.should_not be_false
24
+
25
+ user.key.should == 'test'
26
+ user.admin.should == true
27
+ end
28
+
29
+ it "can find" do
30
+ user = User.create('test')
31
+ User.find('test').should == user
32
+ end
33
+
34
+ it "can find all by key" do
35
+ user = User.create('test')
36
+ User.find_all_by_key(['test']).should == [user]
37
+ end
38
+
39
+ it "returns id as integer" do
40
+ user = User.new
41
+ user.id = 1
42
+ user.save
43
+ user.id.should == 1
44
+ end
45
+
46
+ it "returns id as string" do
47
+ user = User.new
48
+ user.id = 'test'
49
+ user.save
50
+ user.id.should == 'test'
51
+ end
52
+
53
+ it "can update attribute" do
54
+ user = User.new('test', 'admin' => true)
55
+ user.update_attribute(:admin, false)
56
+ user.admin.should be_false
57
+ end
58
+
59
+ it "can update attributes" do
60
+ user = User.new('test', 'admin' => true)
61
+ user.update_attributes({:admin => false})
62
+ user.admin.should be_false
63
+ end
64
+
65
+ context "conflict resolution" do
66
+ let(:key) { 'siblings' }
67
+
68
+ before :each do
69
+ User.new(key, 'age' => 20).save
70
+
71
+ user1 = User[key]
72
+ user2 = User[key]
73
+
74
+ user1.age = 21
75
+ user1.save
76
+
77
+ # no conflict
78
+ User.bucket.get(key).siblings.length.should == 1
79
+
80
+ user2.age = 22
81
+ user2.save
82
+
83
+ # it creates a new sibling because of conflict
84
+ User.bucket.get(key).siblings.length.should == 2
85
+ end
86
+
87
+ it 'it resolves the conflict on risky level' do
88
+ user = User[key]
89
+ User.bucket.get(key).siblings.length.should == 2
90
+ user.riak_object.siblings.length.should == 1
91
+ end
92
+
93
+ it 'resolves the conflict on riak data level' do
94
+ user = User[key]
95
+ user.save
96
+ user.riak_object.siblings.length.should == 1
97
+ User.bucket.get(key).siblings.length.should == 1
98
+ end
99
+ end
100
+ end