risky 1.0.1 → 1.1.0

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,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