cequel 0.5.6 → 1.0.0.pre.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.
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