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