cequel 0.5.6 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cequel.rb +5 -8
  3. data/lib/cequel/errors.rb +1 -0
  4. data/lib/cequel/metal.rb +17 -0
  5. data/lib/cequel/metal/batch.rb +62 -0
  6. data/lib/cequel/metal/cql_row_specification.rb +26 -0
  7. data/lib/cequel/metal/data_set.rb +461 -0
  8. data/lib/cequel/metal/deleter.rb +47 -0
  9. data/lib/cequel/metal/incrementer.rb +35 -0
  10. data/lib/cequel/metal/inserter.rb +53 -0
  11. data/lib/cequel/metal/keyspace.rb +213 -0
  12. data/lib/cequel/metal/row.rb +48 -0
  13. data/lib/cequel/metal/row_specification.rb +37 -0
  14. data/lib/cequel/metal/statement.rb +30 -0
  15. data/lib/cequel/metal/updater.rb +65 -0
  16. data/lib/cequel/metal/writer.rb +73 -0
  17. data/lib/cequel/model.rb +12 -84
  18. data/lib/cequel/model/association_collection.rb +23 -0
  19. data/lib/cequel/model/associations.rb +84 -80
  20. data/lib/cequel/model/base.rb +74 -0
  21. data/lib/cequel/model/belongs_to_association.rb +31 -0
  22. data/lib/cequel/model/callbacks.rb +14 -10
  23. data/lib/cequel/model/collection.rb +255 -0
  24. data/lib/cequel/model/errors.rb +6 -6
  25. data/lib/cequel/model/has_many_association.rb +26 -0
  26. data/lib/cequel/model/mass_assignment.rb +31 -0
  27. data/lib/cequel/model/persistence.rb +119 -115
  28. data/lib/cequel/model/properties.rb +89 -87
  29. data/lib/cequel/model/railtie.rb +21 -14
  30. data/lib/cequel/model/record_set.rb +285 -0
  31. data/lib/cequel/model/schema.rb +33 -0
  32. data/lib/cequel/model/scoped.rb +5 -48
  33. data/lib/cequel/model/validations.rb +18 -18
  34. data/lib/cequel/schema.rb +15 -0
  35. data/lib/cequel/schema/column.rb +135 -0
  36. data/lib/cequel/schema/create_table_dsl.rb +56 -0
  37. data/lib/cequel/schema/keyspace.rb +50 -0
  38. data/lib/cequel/schema/table.rb +120 -0
  39. data/lib/cequel/schema/table_property.rb +67 -0
  40. data/lib/cequel/schema/table_reader.rb +139 -0
  41. data/lib/cequel/schema/table_synchronizer.rb +114 -0
  42. data/lib/cequel/schema/table_updater.rb +83 -0
  43. data/lib/cequel/schema/table_writer.rb +80 -0
  44. data/lib/cequel/schema/update_table_dsl.rb +60 -0
  45. data/lib/cequel/type.rb +232 -0
  46. data/lib/cequel/version.rb +1 -1
  47. data/spec/environment.rb +5 -1
  48. data/spec/examples/metal/data_set_spec.rb +608 -0
  49. data/spec/examples/model/associations_spec.rb +84 -74
  50. data/spec/examples/model/callbacks_spec.rb +66 -59
  51. data/spec/examples/model/list_spec.rb +393 -0
  52. data/spec/examples/model/map_spec.rb +229 -0
  53. data/spec/examples/model/mass_assignment_spec.rb +55 -0
  54. data/spec/examples/model/naming_spec.rb +11 -4
  55. data/spec/examples/model/persistence_spec.rb +140 -150
  56. data/spec/examples/model/properties_spec.rb +122 -75
  57. data/spec/examples/model/record_set_spec.rb +285 -0
  58. data/spec/examples/model/schema_spec.rb +44 -0
  59. data/spec/examples/model/serialization_spec.rb +20 -14
  60. data/spec/examples/model/set_spec.rb +133 -0
  61. data/spec/examples/model/spec_helper.rb +0 -10
  62. data/spec/examples/model/validations_spec.rb +51 -38
  63. data/spec/examples/schema/table_reader_spec.rb +328 -0
  64. data/spec/examples/schema/table_synchronizer_spec.rb +172 -0
  65. data/spec/examples/schema/table_updater_spec.rb +157 -0
  66. data/spec/examples/schema/table_writer_spec.rb +225 -0
  67. data/spec/examples/spec_helper.rb +29 -0
  68. data/spec/examples/type_spec.rb +204 -0
  69. data/spec/support/helpers.rb +67 -8
  70. metadata +121 -152
  71. data/lib/cequel/batch.rb +0 -58
  72. data/lib/cequel/cql_row_specification.rb +0 -22
  73. data/lib/cequel/data_set.rb +0 -371
  74. data/lib/cequel/keyspace.rb +0 -205
  75. data/lib/cequel/model/class_internals.rb +0 -49
  76. data/lib/cequel/model/column.rb +0 -20
  77. data/lib/cequel/model/counter.rb +0 -35
  78. data/lib/cequel/model/dictionary.rb +0 -126
  79. data/lib/cequel/model/dirty.rb +0 -53
  80. data/lib/cequel/model/dynamic.rb +0 -31
  81. data/lib/cequel/model/inheritable.rb +0 -48
  82. data/lib/cequel/model/instance_internals.rb +0 -23
  83. data/lib/cequel/model/local_association.rb +0 -42
  84. data/lib/cequel/model/magic.rb +0 -79
  85. data/lib/cequel/model/mass_assignment_security.rb +0 -21
  86. data/lib/cequel/model/naming.rb +0 -17
  87. data/lib/cequel/model/observer.rb +0 -42
  88. data/lib/cequel/model/readable_dictionary.rb +0 -182
  89. data/lib/cequel/model/remote_association.rb +0 -40
  90. data/lib/cequel/model/scope.rb +0 -362
  91. data/lib/cequel/model/subclass_internals.rb +0 -45
  92. data/lib/cequel/model/timestamps.rb +0 -52
  93. data/lib/cequel/model/translation.rb +0 -17
  94. data/lib/cequel/row_specification.rb +0 -63
  95. data/lib/cequel/statement.rb +0 -23
  96. data/spec/examples/data_set_spec.rb +0 -444
  97. data/spec/examples/keyspace_spec.rb +0 -84
  98. data/spec/examples/model/counter_spec.rb +0 -94
  99. data/spec/examples/model/dictionary_spec.rb +0 -301
  100. data/spec/examples/model/dirty_spec.rb +0 -39
  101. data/spec/examples/model/dynamic_spec.rb +0 -41
  102. data/spec/examples/model/inheritable_spec.rb +0 -45
  103. data/spec/examples/model/magic_spec.rb +0 -199
  104. data/spec/examples/model/mass_assignment_security_spec.rb +0 -13
  105. data/spec/examples/model/observer_spec.rb +0 -86
  106. data/spec/examples/model/scope_spec.rb +0 -677
  107. data/spec/examples/model/timestamps_spec.rb +0 -52
  108. data/spec/examples/model/translation_spec.rb +0 -23
@@ -1,81 +1,128 @@
1
+ require 'cequel/model'
1
2
  require File.expand_path('../spec_helper', __FILE__)
2
3
 
3
4
  describe Cequel::Model::Properties do
4
- let(:post) { Post.new(:id => 1) }
5
5
 
6
- it 'should have getter for key' do
7
- post.id.should == 1
6
+ describe 'property accessors' do
7
+ model :Post do
8
+ key :permalink, :text
9
+ column :title, :text
10
+ list :tags, :text
11
+ set :categories, :text
12
+ map :shares, :text, :int
13
+
14
+ def downcased_title=(downcased_title)
15
+ self.title = downcased_title.titleize
16
+ end
17
+ end
18
+
19
+ it 'should provide accessor for key' do
20
+ Post.new { |post| post.permalink = 'big-data' }.permalink.
21
+ should == 'big-data'
22
+ end
23
+
24
+ it 'should cast key to correct value' do
25
+ Post.new { |post| post.permalink = 44 }.permalink.
26
+ should == '44'
27
+ end
28
+
29
+ it 'should have nil key if unset' do
30
+ Post.new.permalink.should be_nil
31
+ end
32
+
33
+ it 'should provide accessor for data column' do
34
+ Post.new { |post| post.title = 'Big Data' }.title.should == 'Big Data'
35
+ end
36
+
37
+ it 'should cast data column to correct value' do
38
+ Post.new { |post| post.title = 'Big Data'.force_encoding('US-ASCII') }.
39
+ title.encoding.name.should == 'UTF-8'
40
+ end
41
+
42
+ it 'should have nil data column value if unset' do
43
+ Post.new.title.should be_nil
44
+ end
45
+
46
+ it 'should allow setting attributes via #attributes=' do
47
+ Post.new.tap { |post| post.attributes = {:title => 'Big Data' }}.
48
+ title.should == 'Big Data'
49
+ end
50
+
51
+ it 'should use writers when setting attributes' do
52
+ Post.new.tap { |post| post.attributes = {:downcased_title => 'big data' }}.
53
+ title.should == 'Big Data'
54
+ end
55
+
56
+ it 'should take attribute arguments to ::new' do
57
+ Post.new(:downcased_title => 'big data').title.should == 'Big Data'
58
+ end
59
+
60
+ it 'should provide accessor for list column' do
61
+ Post.new { |post| post.tags = %w(one two three) }.tags.
62
+ should == %w(one two three)
63
+ end
64
+
65
+ it 'should cast values in list column' do
66
+ Post.new { |post| post.tags = Set[1, 2, 3] }.tags.
67
+ should == %w(1 2 3)
68
+ end
69
+
70
+ it 'should have empty list column value if unset' do
71
+ Post.new.tags.should == []
72
+ end
73
+
74
+ it 'should provide accessor for set column' do
75
+ Post.new { |post| post.categories = Set['Big Data', 'Cassandra'] }.
76
+ categories.should == Set['Big Data', 'Cassandra']
77
+ end
78
+
79
+ it 'should cast values to correct type' do
80
+ Post.new { |post| post.categories = [1, 2, 3] }.categories.
81
+ should == Set['1', '2', '3']
82
+ end
83
+
84
+ it 'should have empty set column value if present' do
85
+ Post.new.categories.should == Set[]
86
+ end
87
+
88
+ it 'should provide accessor for map column' do
89
+ Post.new { |post| post.shares = {'facebook' => 1, 'twitter' => 2}}.
90
+ shares.should == {'facebook' => 1, 'twitter' => 2}
91
+ end
92
+
93
+ it 'should cast values for map column' do
94
+ Post.new { |post| post.shares = [[:facebook, '1'], [:twitter, '2']] }.
95
+ shares.should == {'facebook' => 1, 'twitter' => 2}
96
+ end
97
+
98
+ it 'should set map column to empty hash by default' do
99
+ Post.new.shares.should == {}
100
+ end
101
+ end
102
+
103
+ describe 'configured property defaults' do
104
+ model :Post do
105
+ key :permalink, :text
106
+ column :title, :text, :default => 'New Post'
107
+ list :tags, :text, :default => ['new']
108
+ set :categories, :text, :default => Set['Big Data']
109
+ map :shares, :text, :int, :default => {'facebook' => 0}
110
+ end
111
+
112
+ it 'should respect default for data column' do
113
+ Post.new.title.should == 'New Post'
114
+ end
115
+
116
+ it 'should respect default for list column' do
117
+ Post.new.tags.should == ['new']
118
+ end
119
+
120
+ it 'should respect default for set column' do
121
+ Post.new.categories.should == Set['Big Data']
122
+ end
123
+
124
+ it 'should respect default for map column' do
125
+ Post.new.shares.should == {'facebook' => 0}
126
+ end
8
127
  end
9
-
10
- it 'should return key alias from class' do
11
- Post.key_alias.should == :id
12
- end
13
-
14
- it 'should return key for to_key' do
15
- post.to_key.should == [1]
16
- end
17
-
18
- it 'should return param for to_param' do
19
- post.persisted!
20
- post.to_param.should == '1'
21
- end
22
-
23
- it 'should have getter and setter for column' do
24
- post.title = 'Object/row mapping'
25
- post.title.should == 'Object/row mapping'
26
- end
27
-
28
- it 'should have ? accessor for boolean column' do
29
- post.published = true
30
- post.published?.should be_true
31
- end
32
-
33
- it 'should not have ? accessor for non-boolean column' do
34
- expect { post.title? }.to raise_error(NoMethodError)
35
- end
36
-
37
- it 'should expose column names on class' do
38
- Post.column_names[0..1].should == [:id, :title]
39
- end
40
-
41
- it 'should expose column objects on class' do
42
- Post.columns[0..1].map { |col| [col.name, col.type] }.
43
- should == [[:id, :int], [:title, :varchar]]
44
- end
45
-
46
- it 'should expose #attributes' do
47
- post.title = 'Cequel'
48
- post.attributes.
49
- should == {:id => 1, :title => 'Cequel'}.with_indifferent_access
50
- end
51
-
52
- it 'should not return nil values with attributes' do
53
- post.title = nil
54
- post.attributes.should == {:id => 1}.with_indifferent_access
55
- end
56
-
57
- it 'should set attributes' do
58
- post.attributes = { :title => 'Cequel' }
59
- post.id.should == 1
60
- post.title.should == 'Cequel'
61
- end
62
-
63
- it 'should set attributes from constructor' do
64
- Post.new(:id => 1, :title => 'Cequel').title.should == 'Cequel'
65
- end
66
-
67
- it 'should set default column values for new instances' do
68
- Blog.new.published.should == true
69
- end
70
-
71
- it 'should use key to compare equality' do
72
- Post.new(:id => 1).should == Post.new(:id => 1)
73
- Post.new(:id => 1).should_not == Post.new(:id => 2)
74
- Post.new(:id => 1).should_not == Blog.new(:id => 1)
75
- end
76
-
77
- it 'should use #generate_key method when implemented' do
78
- Comment.new.id.should be_a(SimpleUUID::UUID)
79
- end
80
-
81
128
  end
@@ -0,0 +1,285 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::RecordSet do
4
+ model :Blog do
5
+ key :subdomain, :text
6
+ column :name, :text
7
+ column :description, :text
8
+ end
9
+
10
+ model :Post do
11
+ key :blog_subdomain, :text
12
+ key :permalink, :text
13
+ column :title, :text
14
+ column :body, :text
15
+ column :author_id, :uuid
16
+ list :tags, :text
17
+ set :categories, :text
18
+ map :shares, :text, :int
19
+ end
20
+
21
+ let(:subdomains) { [] }
22
+
23
+ before do
24
+ cequel.batch do
25
+ 3.times do |i|
26
+ Blog.new do |blog|
27
+ subdomains << blog.subdomain = "blog-#{i}"
28
+ blog.name = "Blog #{i}"
29
+ blog.description = "This is Blog number #{i}"
30
+ end.save
31
+ end
32
+ end
33
+ cequel.batch do
34
+ 5.times do |i|
35
+ cequel[:posts].insert(
36
+ :blog_subdomain => 'cassandra',
37
+ :permalink => "cequel#{i}",
38
+ :title => "Cequel #{i}",
39
+ :body => "Post number #{i}"
40
+ )
41
+ cequel[:posts].insert(
42
+ :blog_subdomain => 'postgres',
43
+ :permalink => "sequel#{i}",
44
+ :title => "Sequel #{i}"
45
+ )
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '::find' do
51
+ context 'simple primary key' do
52
+ subject { Blog.find('blog-0') }
53
+
54
+ its(:subdomain) { should == 'blog-0' }
55
+ its(:name) { should == 'Blog 0' }
56
+
57
+ it { should be_persisted }
58
+ it { should_not be_transient }
59
+ specify { Blog.new.should_not be_persisted }
60
+ specify { Blog.new.should be_transient }
61
+
62
+ specify do
63
+ expect { Blog.find('bogus') }.
64
+ to raise_error(Cequel::Model::RecordNotFound)
65
+ end
66
+ end
67
+
68
+ context 'compound primary key' do
69
+ subject { Post['cassandra'].find('cequel0') }
70
+
71
+ its(:blog_subdomain) { should == 'cassandra' }
72
+ its(:permalink) { should == 'cequel0' }
73
+ its(:title) { should == 'Cequel 0' }
74
+
75
+ it { should be_persisted }
76
+ it { should_not be_transient }
77
+ specify { Post.new.should_not be_persisted }
78
+ specify { Post.new.should be_transient }
79
+
80
+ specify do
81
+ expect { Post['cequel'].find('bogus')}.
82
+ to raise_error(Cequel::Model::RecordNotFound)
83
+ end
84
+ end
85
+ end
86
+
87
+ describe '::[]' do
88
+ context 'simple primary key' do
89
+ subject { Blog['blog-0'] }
90
+
91
+ it 'should not query the database' do
92
+ disallow_queries!
93
+ subject.subdomain.should == 'blog-0'
94
+ end
95
+
96
+ it 'should lazily query the database when attribute accessed' do
97
+ subject.name.should == 'Blog 0'
98
+ end
99
+
100
+ it 'should get all eager-loadable attributes on first lazy load' do
101
+ subject.name
102
+ disallow_queries!
103
+ subject.description.should == 'This is Blog number 0'
104
+ end
105
+ end
106
+
107
+ context 'compound primary key' do
108
+ subject { Post['cassandra']['cequel0'] }
109
+
110
+ it 'should not query the database' do
111
+ expect(cequel).not_to receive(:execute)
112
+ subject.blog_subdomain.should == 'cassandra'
113
+ subject.permalink.should == 'cequel0'
114
+ end
115
+
116
+ it 'should lazily query the database when attribute accessed' do
117
+ subject.title.should == 'Cequel 0'
118
+ end
119
+
120
+ it 'should get all eager-loadable attributes on first lazy load' do
121
+ subject.title
122
+ expect(cequel).not_to receive(:execute)
123
+ subject.body.should == 'Post number 0'
124
+ end
125
+ end
126
+ end
127
+
128
+ describe '#all' do
129
+ it 'should return all the records' do
130
+ Blog.all.map(&:subdomain).should =~ subdomains
131
+ end
132
+ end
133
+
134
+ describe '#find_each' do
135
+ it 'should respect :batch_size argument' do
136
+ cequel.should_receive(:execute).twice.and_call_original
137
+ Blog.find_each(:batch_size => 2).map(&:subdomain).
138
+ should =~ subdomains
139
+ end
140
+ it 'should iterate over all keys' do
141
+ Post.find_each(:batch_size => 2).map(&:title).
142
+ should =~ (0...5).flat_map { |i| ["Cequel #{i}", "Sequel #{i}"] }
143
+ end
144
+ end
145
+
146
+ describe '#at' do
147
+ it 'should return partial collection' do
148
+ Post.at('cassandra').find_each(:batch_size => 2).map(&:title).
149
+ should == (0...5).map { |i| "Cequel #{i}" }
150
+ end
151
+ end
152
+
153
+ describe '#[]' do
154
+ it 'should create partial collection if not all keys specified' do
155
+ Post['cassandra'].find_each(:batch_size => 2).map(&:title).
156
+ should == (0...5).map { |i| "Cequel #{i}" }
157
+ end
158
+ end
159
+
160
+ describe '#/' do
161
+ it 'should behave like #at' do
162
+ (Post / 'cassandra').find_each(:batch_size => 2).map(&:title).
163
+ should == (0...5).map { |i| "Cequel #{i}" }
164
+ end
165
+ end
166
+
167
+ describe '#after' do
168
+ it 'should return collection after given key' do
169
+ Post.at('cassandra').after('cequel1').map(&:title).
170
+ should == (2...5).map { |i| "Cequel #{i}" }
171
+ end
172
+ end
173
+
174
+ describe '#from' do
175
+ it 'should return collection starting with given key' do
176
+ Post.at('cassandra').from('cequel1').map(&:title).
177
+ should == (1...5).map { |i| "Cequel #{i}" }
178
+ end
179
+
180
+ it 'should raise ArgumentError when called on partition key' do
181
+ expect { Post.from('cassandra') }.to raise_error(NoMethodError)
182
+ end
183
+ end
184
+
185
+ describe '#before' do
186
+ it 'should return collection before given key' do
187
+ Post.at('cassandra').before('cequel3').map(&:title).
188
+ should == (0...3).map { |i| "Cequel #{i}" }
189
+ end
190
+ end
191
+
192
+ describe '#upto' do
193
+ it 'should return collection up to given key' do
194
+ Post.at('cassandra').upto('cequel3').map(&:title).
195
+ should == (0..3).map { |i| "Cequel #{i}" }
196
+ end
197
+ end
198
+
199
+ describe '#in' do
200
+ it 'should return collection with inclusive upper bound' do
201
+ Post.at('cassandra').in('cequel1'..'cequel3').map(&:title).
202
+ should == (1..3).map { |i| "Cequel #{i}" }
203
+ end
204
+
205
+ it 'should return collection with exclusive upper bound' do
206
+ Post.at('cassandra').in('cequel1'...'cequel3').map(&:title).
207
+ should == (1...3).map { |i| "Cequel #{i}" }
208
+ end
209
+ end
210
+
211
+ describe '#reverse' do
212
+ it 'should not call the database' do
213
+ disallow_queries!
214
+ Post.at('cassandra').reverse
215
+ end
216
+
217
+ it 'should return collection in reverse' do
218
+ Post.at('cassandra').reverse.map(&:title).
219
+ should == (0...5).map { |i| "Cequel #{i}" }.reverse
220
+ end
221
+
222
+ it 'should batch iterate over collection in reverse' do
223
+ Post.at('cassandra').reverse.find_each(:batch_size => 2).map(&:title).
224
+ should == (0...5).map { |i| "Cequel #{i}" }.reverse
225
+ end
226
+
227
+ it 'should raise an error if range key is a partition key' do
228
+ expect { Post.all.reverse }.to raise_error(NoMethodError)
229
+ end
230
+ end
231
+
232
+ describe 'last' do
233
+ it 'should return the last instance' do
234
+ Post.at('cassandra').last.title.should == "Cequel 4"
235
+ end
236
+ end
237
+
238
+ describe '#first' do
239
+ context 'with no arguments' do
240
+ it 'should return an arbitrary record' do
241
+ subdomains.should include(Blog.first.subdomain)
242
+ end
243
+ end
244
+
245
+ context 'with a given size' do
246
+ subject { Blog.first(2) }
247
+
248
+ it { should be_a(Array) }
249
+ it { should have(2).items }
250
+ specify { (subject.map(&:subdomain) & subdomains).should have(2).items }
251
+ end
252
+ end
253
+
254
+ describe '#limit' do
255
+ it 'should return the number of blogs requested' do
256
+ Blog.limit(2).should have(2).entries
257
+ end
258
+ end
259
+
260
+ describe '#select' do
261
+ context 'with no block' do
262
+ subject { Blog.select(:subdomain, :name).first }
263
+
264
+ it { should be_loaded(:name) }
265
+ it { should_not be_loaded(:description) }
266
+ specify { expect { subject.name }.to_not raise_error }
267
+ specify { expect { subject.description }.
268
+ to raise_error(Cequel::Model::MissingAttributeError) }
269
+ end
270
+
271
+ context 'with block' do
272
+ it 'should delegate to the Enumerable method' do
273
+ Blog.all.select { |p| p.subdomain[/\d+/].to_i.even? }.
274
+ map(&:subdomain).should =~ %w(blog-0 blog-2)
275
+ end
276
+ end
277
+ end
278
+
279
+ describe '#count' do
280
+ it 'should count records' do
281
+ Blog.count.should == 3
282
+ end
283
+ end
284
+
285
+ end