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,109 +1,119 @@
1
- require File.expand_path('../spec_helper', __FILE__)
1
+ require_relative 'spec_helper'
2
2
 
3
3
  describe Cequel::Model::Associations do
4
4
 
5
+ model :Blog do
6
+ key :subdomain, :text
7
+ column :name, :text
8
+
9
+ has_many :posts
10
+ end
11
+
12
+ model :User do
13
+ key :login, :text
14
+ column :name, :text
15
+ end
16
+
17
+ model :Post do
18
+ belongs_to :blog
19
+ key :id, :uuid
20
+ column :title, :text
21
+ end
22
+
5
23
  describe '::belongs_to' do
6
- let(:post) do
7
- Post.new(:id => 1).tap { |post| post.blog_id = 2 }
8
- end
24
+ let(:blog) { Blog.new { |blog| blog.subdomain = 'big-data' }}
25
+ let(:post) { Post.new }
9
26
 
10
- before do
11
- connection.stub(:execute).
12
- with('SELECT * FROM blogs WHERE ? = ? LIMIT 1', :id, 2).
13
- and_return result_stub(:id => 2, :name => 'Big Data Blog')
27
+ it 'should add parent key as first key' do
28
+ Post.key_column_names.first.should == :blog_subdomain
14
29
  end
15
30
 
16
- it 'should query column family and return associated model' do
17
- post.blog.name.should == 'Big Data Blog'
31
+ it 'should provide accessors for association object' do
32
+ post.blog = blog
33
+ post.blog.should == blog
18
34
  end
19
35
 
20
- it 'should be nil if foreign key column is nil' do
21
- post.blog_id = nil
22
- post.blog.should be_nil
36
+ it 'should set parent key(s) when setting association object' do
37
+ post.blog = blog
38
+ post.blog_subdomain.should == 'big-data'
23
39
  end
24
40
 
25
- it 'should memoize instance' do
26
- post.blog
27
- connection.should_not_receive :execute
28
- post.blog
41
+ it 'should raise ArgumentError when parent is set without keys' do
42
+ blog.subdomain = nil
43
+ expect { post.blog = blog }.to raise_error(ArgumentError)
29
44
  end
30
45
 
31
- it 'should unmemoize instance if foreign key is changed' do
32
- post.blog
33
- post.blog_id = 3
34
- connection.stub(:execute).
35
- with('SELECT * FROM blogs WHERE ? = ? LIMIT 1', :id, 3).
36
- and_return result_stub(:id => 2, :name => 'Another Blog')
46
+ it 'should raise ArgumentError when parent is set to wrong class' do
47
+ expect { post.blog = post }.to raise_error(ArgumentError)
48
+ end
37
49
 
38
- post.blog.name.should == 'Another Blog'
50
+ it 'should return Blog instance when parent key set directly' do
51
+ post.blog_subdomain = 'big-data'
52
+ post.blog.subdomain.should == 'big-data'
39
53
  end
40
54
 
41
- it 'should provide setter for association' do
42
- post.blog = Blog.new(:id => 3, :name => 'This blog')
43
- post.blog_id.should == 3
55
+ it 'should not hydrate parent instance when creating from key' do
56
+ post.blog_subdomain = 'big-data'
57
+ disallow_queries!
58
+ post.blog.should_not be_loaded
44
59
  end
45
60
 
46
- it 'should set foreign key to nil if association set to nil' do
47
- post.blog = Blog.new(:id => 3, :name => 'This blog')
48
- post.blog = nil
49
- post.blog_id.should be_nil
61
+ it 'should not allow declaring belongs_to after key' do
62
+ expect do
63
+ Class.new(Cequel::Model::Base) do
64
+ key :permalink, :text
65
+ belongs_to :blog
66
+ end
67
+ end.to raise_error(Cequel::Model::InvalidRecordConfiguration)
50
68
  end
51
69
 
52
- it 'should save transient associated object' do
53
- now = Time.now
54
- Time.stub(:now).and_return now
55
- post.blog = Blog.new(:id => 3, :name => 'This blog')
56
- connection.should_receive(:execute).
57
- with "INSERT INTO blogs (?) VALUES (?)",
58
- ['id', 'published', 'name', 'updated_at', 'created_at'],
59
- [3, true, 'This blog', now, now]
60
- connection.stub(:execute).
61
- with "INSERT INTO posts (?) VALUES (?)", ['id', 'blog_id'], [post.id, 3]
62
- post.save
70
+ it 'should not allow declaring belongs_to more than once' do
71
+ expect do
72
+ Class.new(Cequel::Model::Base) do
73
+ belongs_to :blog
74
+ belongs_to :user
75
+ end
76
+ end.to raise_error(Cequel::Model::InvalidRecordConfiguration)
63
77
  end
64
78
 
65
79
  end
66
80
 
67
81
  describe '::has_many' do
68
- let(:blog) do
69
- Blog.new(:id => 2)
82
+ let(:blog) { Blog.new { |blog| blog.subdomain = 'cequel' }.tap(&:save) }
83
+ let!(:posts) do
84
+ 3.times.map do |i|
85
+ Post.new do |post|
86
+ post.blog = blog
87
+ post.id = CassandraCQL::UUID.new
88
+ post.title = "Post #{i}"
89
+ end.tap(&:save)
90
+ end
70
91
  end
71
-
72
- before do
73
- connection.stub(:execute).
74
- with('SELECT * FROM posts WHERE ? = ?', :blog_id, 2).
75
- and_return result_stub(
76
- {:id => 1, :title => 'Cequel'},
77
- {:id => 2, :title => 'Cequel revisited'}
78
- )
92
+ let!(:other_posts) do
93
+ 3.times.map do |i|
94
+ Post.new do |post|
95
+ post.blog_subdomain = 'mycat'
96
+ post.id = CassandraCQL::UUID.new
97
+ post.title = "My Cat #{i}"
98
+ end.tap(&:save)
99
+ end
79
100
  end
80
101
 
81
- it 'should provide scope for associated instances' do
82
- blog.posts.map { |post| post.title }.should ==
83
- ['Cequel', 'Cequel revisited']
102
+ it 'should return scope of posts' do
103
+ blog.posts.map(&:title).should == ["Post 0", "Post 1", "Post 2"]
84
104
  end
85
105
 
86
- it 'should destroy associated instances if :dependent => :destroy' do
87
- connection.stub(:execute).with 'DELETE FROM blogs WHERE ? = ?', :id, 2
88
- connection.should_receive(:execute).with 'DELETE FROM posts WHERE ? = ?', :id, 1
89
- connection.should_receive(:execute).with 'DELETE FROM posts WHERE ? = ?', :id, 2
90
- blog.destroy
106
+ it 'should retain scope when hydrated multiple times' do
107
+ blog.posts.map(&:id)
108
+ disallow_queries!
109
+ blog.posts.map(&:title).should == ["Post 0", "Post 1", "Post 2"]
91
110
  end
92
- end
93
-
94
- describe '::has_one' do
95
- let(:post) { Post.new(:id => 1) }
96
111
 
97
- before do
98
- connection.stub(:execute).
99
- with("SELECT * FROM assets WHERE ? = ? AND ? = ? LIMIT 1", :class_name, 'Photo', :post_id, 1).
100
- and_return result_stub(
101
- {:id => 1, :type => 'Photo', :url => 'http://outofti.me/glamour.jpg'},
102
- )
103
- end
104
-
105
- it 'should look up association by foreign key' do
106
- post.thumbnail.url.should == 'http://outofti.me/glamour.jpg'
112
+ it 'should reload when reload argument passed' do
113
+ blog.posts.map(&:id)
114
+ posts.first.destroy
115
+ blog.posts(true).map(&:title).should == ['Post 1', 'Post 2']
107
116
  end
108
117
  end
118
+
109
119
  end
@@ -1,79 +1,86 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
3
  describe Cequel::Model::Callbacks do
4
- let(:post) do
5
- connection.stub(:execute).
6
- with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 1).
7
- and_return result_stub(:id => 1, :title => 'Cequel')
8
- Post.find(1)
9
- end
10
-
11
- context 'on create' do
12
- let(:post) { Post.new(:id => 1) }
13
-
14
- before do
15
- post.save
4
+ model :Post do
5
+ key :permalink, :text
6
+ column :title, :text
7
+
8
+ def self.track_callbacks(*events)
9
+ events.each do |event|
10
+ %w(before after).each do |position|
11
+ callback_name = :"#{position}_#{event}"
12
+ __send__(callback_name) do |post|
13
+ post.executed_callbacks << callback_name
14
+ end
15
+ end
16
+ end
16
17
  end
17
18
 
18
- it 'should invoke save callback' do
19
- post.should have_callback(:save)
20
- end
21
-
22
- it 'should invoke create callback' do
23
- post.should have_callback(:create)
24
- end
19
+ track_callbacks :save, :create, :update, :destroy
25
20
 
26
- it 'should not invoke update callback' do
27
- post.should_not have_callback(:update)
21
+ def executed_callbacks
22
+ @executed_callbacks ||= []
28
23
  end
29
24
 
30
- it 'should not invoke destroy callback' do
31
- post.should_not have_callback(:destroy)
32
- end
33
25
  end
34
26
 
35
- context 'on update' do
36
- before do
37
- post.save
38
- end
39
-
40
- it 'should invoke save callback' do
41
- post.should have_callback(:save)
27
+ let(:new_post) do
28
+ Post.new do |post|
29
+ post.permalink = 'new-post'
30
+ post.title = 'New Post'
42
31
  end
32
+ end
43
33
 
44
- it 'should not invoke create callback' do
45
- post.should_not have_callback(:create)
34
+ let!(:existing_post) do
35
+ Post.new do |post|
36
+ post.permalink = 'existing-post'
37
+ post.title = 'Existing Post'
38
+ end.save!
39
+ Post.find('existing-post').tap do |post|
40
+ post.title = 'An Existing Post'
46
41
  end
42
+ end
47
43
 
48
- it 'should invoke update callback' do
49
- post.should have_callback(:update)
50
- end
44
+ context 'on create' do
45
+ before { new_post.save! }
46
+ subject { new_post.executed_callbacks }
47
+
48
+ it { should include(:before_save) }
49
+ it { should include(:after_save) }
50
+ it { should include(:before_create) }
51
+ it { should include(:after_create) }
52
+ it { should_not include(:before_update) }
53
+ it { should_not include(:after_update) }
54
+ it { should_not include(:before_destroy) }
55
+ it { should_not include(:after_destroy) }
56
+ end
51
57
 
52
- it 'should not invoke destroy callback' do
53
- post.should_not have_callback(:destroy)
54
- end
58
+ context 'on update' do
59
+ before { existing_post.save! }
60
+ subject { existing_post.executed_callbacks }
61
+
62
+ it { should include(:before_save) }
63
+ it { should include(:after_save) }
64
+ it { should_not include(:before_create) }
65
+ it { should_not include(:after_create) }
66
+ it { should include(:before_update) }
67
+ it { should include(:after_update) }
68
+ it { should_not include(:before_destroy) }
69
+ it { should_not include(:after_destroy) }
55
70
  end
56
71
 
57
72
  context 'on destroy' do
58
- before do
59
- connection.stub(:execute).with("DELETE FROM posts WHERE ? = ?", :id, 1)
60
- post.destroy
61
- end
62
-
63
- it 'should not invoke save callback' do
64
- post.should_not have_callback(:save)
65
- end
66
-
67
- it 'should not invoke create callback' do
68
- post.should_not have_callback(:create)
69
- end
70
-
71
- it 'should not invoke update callback' do
72
- post.should_not have_callback(:update)
73
- end
74
-
75
- it 'should invoke destroy callback' do
76
- post.should have_callback(:destroy)
77
- end
73
+ before { existing_post.destroy }
74
+
75
+ subject { existing_post.executed_callbacks }
76
+
77
+ it { should_not include(:before_save) }
78
+ it { should_not include(:after_save) }
79
+ it { should_not include(:before_create) }
80
+ it { should_not include(:after_create) }
81
+ it { should_not include(:before_update) }
82
+ it { should_not include(:after_update) }
83
+ it { should include(:before_destroy) }
84
+ it { should include(:after_destroy) }
78
85
  end
79
86
  end
@@ -0,0 +1,393 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::List do
4
+ model :Post do
5
+ key :permalink, :text
6
+ column :title, :text
7
+ list :tags, :text
8
+ end
9
+
10
+ let(:scope) { cequel[:posts].where(:permalink => 'cequel') }
11
+ subject { scope.first }
12
+
13
+ let! :post do
14
+ Post.new do |post|
15
+ post.permalink = 'cequel'
16
+ post.tags = %w(one two)
17
+ end.tap(&:save)
18
+ end
19
+
20
+ let! :unloaded_post do
21
+ Post['cequel']
22
+ end
23
+
24
+ context 'new record' do
25
+ it 'should save list as-is' do
26
+ subject[:tags].should == %w(one two)
27
+ end
28
+ end
29
+
30
+ describe '#<<' do
31
+ it 'should add new items' do
32
+ post.tags << 'three' << 'four'
33
+ post.save
34
+ subject[:tags].should == %w(one two three four)
35
+ end
36
+
37
+ it 'should add new items atomically' do
38
+ scope.list_append(:tags, 'three')
39
+ post.tags << 'four' << 'five'
40
+ post.save
41
+ subject[:tags].should == %w(one two three four five)
42
+ end
43
+
44
+ it 'should add new items without reading' do
45
+ unloaded_post.tags << 'four' << 'five'
46
+ unloaded_post.save
47
+ unloaded_post.should_not be_loaded
48
+ subject[:tags].should == %w(one two four five)
49
+ end
50
+
51
+ it 'should add new items in memory when loaded' do
52
+ unloaded_post.tags << 'four' << 'five'
53
+ unloaded_post.tags.should == %w(one two four five)
54
+ end
55
+ end
56
+
57
+ describe '#[]=' do
58
+ before { scope.list_append(:tags, 'three') }
59
+
60
+ it 'should atomically replace a single element' do
61
+ post.tags[1] = 'TWO'
62
+ post.save
63
+ subject[:tags].should == %w(one TWO three)
64
+ post.tags.should == %w(one TWO)
65
+ end
66
+
67
+ it 'should replace an element without reading' do
68
+ cequel.should_not_receive :execute
69
+ unloaded_post.tags[1] = 'TWO'
70
+ end
71
+
72
+ it 'should persist the replaced element' do
73
+ unloaded_post.tags[1] = 'TWO'
74
+ unloaded_post.save
75
+ subject[:tags].should == %w(one TWO three)
76
+ end
77
+
78
+ it 'should apply local modifications when loaded later' do
79
+ unloaded_post.tags[1] = 'TWO'
80
+ unloaded_post.tags.should == %w(one TWO three)
81
+ end
82
+
83
+ it 'should atomically replace a given number of arguments' do
84
+ post.tags[0, 2] = 'One', 'Two'
85
+ post.save
86
+ subject[:tags].should == %w(One Two three)
87
+ post.tags.should == %w(One Two)
88
+ end
89
+
90
+ it 'should remove elements beyond positional arguments' do
91
+ scope.list_append(:tags, 'four')
92
+ post.tags[0, 3] = 'ONE'
93
+ post.save
94
+ subject[:tags].should == %w(ONE four)
95
+ post.tags.should == %w(ONE)
96
+ end
97
+
98
+ it 'should atomically replace a given range of elements' do
99
+ post.tags[0..1] = ['One', 'Two']
100
+ post.save
101
+ subject[:tags].should == %w(One Two three)
102
+ post.tags.should == %w(One Two)
103
+ end
104
+
105
+ it 'should remove elements beyond positional arguments' do
106
+ scope.list_append(:tags, 'four')
107
+ post.tags[0..2] = 'ONE'
108
+ post.save
109
+ subject[:tags].should == %w(ONE four)
110
+ post.tags.should == %w(ONE)
111
+ end
112
+ end
113
+
114
+ describe '#clear' do
115
+ it 'should clear all elements from the array' do
116
+ post.tags.clear
117
+ post.save
118
+ subject[:tags].should be_blank
119
+ post.tags.should == []
120
+ end
121
+
122
+ it 'should clear elements without loading' do
123
+ cequel.should_not receive(:execute)
124
+ unloaded_post.tags.clear
125
+ end
126
+
127
+ it 'should persist clear without loading' do
128
+ unloaded_post.tags.clear
129
+ unloaded_post.save
130
+ subject[:tags].should be_blank
131
+ end
132
+
133
+ it 'should apply local modifications post-hoc' do
134
+ unloaded_post.tags.clear
135
+ unloaded_post.tags.should == []
136
+ end
137
+ end
138
+
139
+ describe '#collect!' do
140
+ it 'should not respond' do
141
+ expect { post.tags.collect!(&:upcase) }.to raise_error(NoMethodError)
142
+ end
143
+ end
144
+
145
+ describe '#concat' do
146
+ it 'should atomically concatenate elements' do
147
+ scope.list_append(:tags, 'three')
148
+ post.tags.concat(['four', 'five'])
149
+ post.save
150
+ subject[:tags].should == %w(one two three four five)
151
+ post.tags.should == %w(one two four five)
152
+ end
153
+
154
+ it 'should concat elements without loading' do
155
+ cequel.should_not_receive :execute
156
+ unloaded_post.tags.concat(['four', 'five'])
157
+ end
158
+
159
+ it 'should persist concatenated elements' do
160
+ unloaded_post.tags.concat(['four', 'five'])
161
+ unloaded_post.save
162
+ subject[:tags].should == %w(one two four five)
163
+ end
164
+
165
+ it 'should apply local modifications when loaded later' do
166
+ unloaded_post.tags.concat(['four', 'five'])
167
+ unloaded_post.tags.should == %w(one two four five)
168
+ end
169
+ end
170
+
171
+ describe '#delete' do
172
+ it 'should atomically delete all instances of an object' do
173
+ scope.list_append(:tags, 'three')
174
+ scope.list_append(:tags, 'two')
175
+ post.tags.delete('two')
176
+ post.save
177
+ subject[:tags].should == %w(one three)
178
+ post.tags.should == %w(one)
179
+ end
180
+
181
+ it 'should delete without loading' do
182
+ cequel.should_not_receive :execute
183
+ unloaded_post.tags.delete('two')
184
+ end
185
+
186
+ it 'should persist deletions without loading' do
187
+ unloaded_post.tags.delete('two')
188
+ unloaded_post.save
189
+ subject[:tags].should == %w(one)
190
+ end
191
+
192
+ it 'should modify local copy after the fact' do
193
+ unloaded_post.tags.delete('two')
194
+ unloaded_post.tags.should == %w(one)
195
+ end
196
+ end
197
+
198
+ describe '#delete_at' do
199
+ it 'should atomically delete from a given index' do
200
+ scope.list_append(:tags, ['three', 'four'])
201
+ post.tags.delete_at(1)
202
+ post.save
203
+ subject[:tags].should == %w(one three four)
204
+ post.tags.should == %w(one)
205
+ end
206
+
207
+ it 'should delete from a given index without reading' do
208
+ cequel.should_not_receive :execute
209
+ unloaded_post.tags.delete_at(1)
210
+ end
211
+
212
+ it 'should persist deletion from unloaded list' do
213
+ unloaded_post.tags.delete_at(1)
214
+ unloaded_post.save
215
+ subject[:tags].should == %w(one)
216
+ end
217
+
218
+ it 'should apply deletion after the fact' do
219
+ unloaded_post.tags.delete_at(1)
220
+ unloaded_post.tags.should == %w(one)
221
+ end
222
+ end
223
+
224
+ describe '#delete_if' do
225
+ it 'should not respond' do
226
+ expect { post.tags.delete_if { |tag| tag.start_with?('o') } }.
227
+ to raise_error(NoMethodError)
228
+ end
229
+ end
230
+
231
+ describe '#fill' do
232
+ it 'should not respond' do
233
+ expect { post.tags.fill('seventy') }.to raise_error(NoMethodError)
234
+ end
235
+ end
236
+
237
+ describe '#flatten!' do
238
+ it 'should not respond' do
239
+ expect { post.tags.flatten! }.to raise_error(NoMethodError)
240
+ end
241
+ end
242
+
243
+ describe '#insert' do
244
+ it 'should not respond' do
245
+ expect { post.tags.insert(4, 'five') }.to raise_error(NoMethodError)
246
+ end
247
+ end
248
+
249
+ describe '#keep_if' do
250
+ it 'should not respond' do
251
+ expect { post.tags.keep_if { |e| e.start_with?('o') } }.
252
+ to raise_error(NoMethodError)
253
+ end
254
+ end
255
+
256
+ describe '#map!' do
257
+ it 'should not respond' do
258
+ expect { post.tags.map! { |e| e.upcase } }.
259
+ to raise_error(NoMethodError)
260
+ end
261
+ end
262
+
263
+ describe '#pop' do
264
+ it 'should not respond' do
265
+ expect { post.tags.pop }.to raise_error(NoMethodError)
266
+ end
267
+ end
268
+
269
+ describe '#push' do
270
+ it 'should add new items atomically' do
271
+ scope.list_append(:tags, 'three')
272
+ post.tags.push('four').push('five')
273
+ post.save
274
+ subject[:tags].should == %w(one two three four five)
275
+ post.tags.should == %w(one two four five)
276
+ end
277
+ end
278
+
279
+ describe '#reject!' do
280
+ it 'should not respond' do
281
+ expect { post.tags.reject! { |e| e.start_with?('o') } }.
282
+ to raise_error(NoMethodError)
283
+ end
284
+ end
285
+
286
+ describe '#replace' do
287
+ it 'should just overwrite the whole array' do
288
+ scope.list_append(:tags, 'three')
289
+ post.tags.replace(%w(four five))
290
+ post.save
291
+ subject[:tags].should == %w(four five)
292
+ post.tags.should == %w(four five)
293
+ end
294
+
295
+ it 'should overwrite without reading' do
296
+ cequel.should_not_receive :execute
297
+ unloaded_post.tags.replace(%w(four five))
298
+ end
299
+
300
+ it 'should persist unloaded overwrite' do
301
+ unloaded_post.tags.replace(%w(four five))
302
+ unloaded_post.save
303
+ subject[:tags].should == %w(four five)
304
+ end
305
+
306
+ it 'should apply replace post-hoc' do
307
+ unloaded_post.tags.replace(%w(four five))
308
+ unloaded_post.tags.should == %w(four five)
309
+ end
310
+ end
311
+
312
+ describe '#reverse!' do
313
+ it 'should not respond' do
314
+ expect { post.tags.reverse! }.to raise_error(NoMethodError)
315
+ end
316
+ end
317
+
318
+ describe '#rotate!' do
319
+ it 'should not respond' do
320
+ expect { post.tags.rotate! }.to raise_error(NoMethodError)
321
+ end
322
+ end
323
+
324
+ describe '#select!' do
325
+ it 'should not respond' do
326
+ expect { post.tags.select! { |e| e.start_with?('o') } }.
327
+ to raise_error(NoMethodError)
328
+ end
329
+ end
330
+
331
+ describe '#shift' do
332
+ it 'should not respond' do
333
+ expect { post.tags.shift }.to raise_error(NoMethodError)
334
+ end
335
+ end
336
+
337
+ describe '#shuffle!' do
338
+ it 'should not respond' do
339
+ expect { post.tags.shuffle! }.to raise_error(NoMethodError)
340
+ end
341
+ end
342
+
343
+ describe '#slice!' do
344
+ it 'should not respond' do
345
+ expect { post.tags.slice!(1, 2) }.to raise_error(NoMethodError)
346
+ end
347
+ end
348
+
349
+ describe '#sort!' do
350
+ it 'should not respond' do
351
+ expect { post.tags.sort! }.to raise_error(NoMethodError)
352
+ end
353
+ end
354
+
355
+ describe '#sort_by!' do
356
+ it 'should not respond' do
357
+ expect { post.tags.sort_by! { |e| e.reverse } }.
358
+ to raise_error(NoMethodError)
359
+ end
360
+ end
361
+
362
+ describe '#uniq!' do
363
+ it 'should not respond' do
364
+ expect { post.tags.uniq! }.to raise_error(NoMethodError)
365
+ end
366
+ end
367
+
368
+ describe '#unshift' do
369
+ it 'should atomically unshift' do
370
+ scope.list_prepend(:tags, 'zero')
371
+ post.tags.unshift('minustwo', 'minusone')
372
+ post.save
373
+ subject[:tags].should == %w(minustwo minusone zero one two)
374
+ post.tags.should == %w(minustwo minusone one two)
375
+ end
376
+
377
+ it 'should unshift without reading' do
378
+ cequel.should_not_receive :execute
379
+ unloaded_post.tags.unshift('minustwo', 'minusone')
380
+ end
381
+
382
+ it 'should persist unloaded unshift' do
383
+ unloaded_post.tags.unshift('minustwo', 'minusone')
384
+ unloaded_post.save
385
+ subject[:tags].should == %w(minustwo minusone one two)
386
+ end
387
+
388
+ it 'should apply unshift after the fact' do
389
+ unloaded_post.tags.unshift('minustwo', 'minusone')
390
+ unloaded_post.tags.should == %w(minustwo minusone one two)
391
+ end
392
+ end
393
+ end