cassandra_mapper 0.0.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.
- data/README.rdoc +98 -0
- data/Rakefile.rb +11 -0
- data/lib/cassandra_mapper.rb +5 -0
- data/lib/cassandra_mapper/base.rb +19 -0
- data/lib/cassandra_mapper/connection.rb +9 -0
- data/lib/cassandra_mapper/core_ext/array/extract_options.rb +29 -0
- data/lib/cassandra_mapper/core_ext/array/wrap.rb +22 -0
- data/lib/cassandra_mapper/core_ext/class/inheritable_attributes.rb +232 -0
- data/lib/cassandra_mapper/core_ext/kernel/reporting.rb +62 -0
- data/lib/cassandra_mapper/core_ext/kernel/singleton_class.rb +13 -0
- data/lib/cassandra_mapper/core_ext/module/aliasing.rb +70 -0
- data/lib/cassandra_mapper/core_ext/module/attribute_accessors.rb +66 -0
- data/lib/cassandra_mapper/core_ext/object/duplicable.rb +65 -0
- data/lib/cassandra_mapper/core_ext/string/inflections.rb +160 -0
- data/lib/cassandra_mapper/core_ext/string/multibyte.rb +72 -0
- data/lib/cassandra_mapper/exceptions.rb +10 -0
- data/lib/cassandra_mapper/identity.rb +29 -0
- data/lib/cassandra_mapper/indexing.rb +465 -0
- data/lib/cassandra_mapper/observable.rb +36 -0
- data/lib/cassandra_mapper/persistence.rb +309 -0
- data/lib/cassandra_mapper/support/callbacks.rb +136 -0
- data/lib/cassandra_mapper/support/concern.rb +31 -0
- data/lib/cassandra_mapper/support/dependencies.rb +60 -0
- data/lib/cassandra_mapper/support/descendants_tracker.rb +41 -0
- data/lib/cassandra_mapper/support/inflections.rb +58 -0
- data/lib/cassandra_mapper/support/inflector.rb +7 -0
- data/lib/cassandra_mapper/support/inflector/inflections.rb +213 -0
- data/lib/cassandra_mapper/support/inflector/methods.rb +143 -0
- data/lib/cassandra_mapper/support/inflector/transliterate.rb +99 -0
- data/lib/cassandra_mapper/support/multibyte.rb +46 -0
- data/lib/cassandra_mapper/support/multibyte/utils.rb +62 -0
- data/lib/cassandra_mapper/support/observing.rb +218 -0
- data/lib/cassandra_mapper/support/support_callbacks.rb +593 -0
- data/test/test_helper.rb +11 -0
- data/test/unit/callbacks_test.rb +100 -0
- data/test/unit/identity_test.rb +51 -0
- data/test/unit/indexing_test.rb +406 -0
- data/test/unit/observer_test.rb +56 -0
- data/test/unit/persistence_test.rb +561 -0
- metadata +192 -0
data/test/test_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CallbacksTest < Test::Unit::TestCase
|
4
|
+
context 'A CassandraModel::Base-derived instance' do
|
5
|
+
setup do
|
6
|
+
@class = Class.new(CassandraMapper::Base) do
|
7
|
+
before_save :do_before_save
|
8
|
+
after_save :do_after_save
|
9
|
+
before_create :do_before_create
|
10
|
+
after_create :do_after_create
|
11
|
+
before_update :do_before_update
|
12
|
+
after_update :do_after_update
|
13
|
+
before_destroy :do_before_destroy
|
14
|
+
after_destroy :do_after_destroy
|
15
|
+
end
|
16
|
+
@instance = @class.new
|
17
|
+
@connection = stub('connection')
|
18
|
+
@instance.stubs(:connection).returns(@connection)
|
19
|
+
@sequence = sequence('invocation_sequence')
|
20
|
+
@simple_representation = {}
|
21
|
+
@instance.stubs(:to_simple).returns(@simple_representation)
|
22
|
+
@instance.stubs(:key).returns(:foo)
|
23
|
+
# intercept freeze invocations to prevent Mocha teardown errors
|
24
|
+
@instance.stubs(:freeze)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'first save' do
|
28
|
+
setup do
|
29
|
+
@instance.stubs(:new_record?).returns(true)
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'invoke the before_save, before_create, after_create, after_save callbacks' do
|
33
|
+
@instance.expects(:do_before_save).in_sequence(@sequence).returns(true)
|
34
|
+
@instance.expects(:do_before_create).in_sequence(@sequence).returns(true)
|
35
|
+
@connection.expects(:insert).in_sequence(@sequence).returns(@instance)
|
36
|
+
@instance.expects(:do_after_create).in_sequence(@sequence).returns(true)
|
37
|
+
@instance.expects(:do_after_save).in_sequence(@sequence).returns(true)
|
38
|
+
@instance.save
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'update' do
|
43
|
+
setup do
|
44
|
+
@instance.stubs(:new_record?).returns(false)
|
45
|
+
@instance.stubs(:changed_attributes).returns([:blah])
|
46
|
+
# must provide an actual new value for an insert to take place
|
47
|
+
@simple_representation['blah'] = 'foo'
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'invoke the before_save, before_update, after_update, after_save callbacks' do
|
51
|
+
@instance.expects(:do_before_save).in_sequence(@sequence).returns(true)
|
52
|
+
@instance.expects(:do_before_update).in_sequence(@sequence).returns(true)
|
53
|
+
@connection.expects(:insert).in_sequence(@sequence).returns(@instance)
|
54
|
+
@instance.expects(:do_after_update).in_sequence(@sequence).returns(true)
|
55
|
+
@instance.expects(:do_after_save).in_sequence(@sequence).returns(true)
|
56
|
+
@instance.save
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'after being retrieved via :find' do
|
61
|
+
setup do
|
62
|
+
@key = 'some_key'
|
63
|
+
@class.maps :some_attr
|
64
|
+
@source_values = {'some_attr' => 'some_value'}
|
65
|
+
@connection = stub('connection', :multi_get => {@key => @source_values})
|
66
|
+
@class.stubs(:connection).returns(@connection)
|
67
|
+
@class.module_eval do
|
68
|
+
after_load :do_after_load
|
69
|
+
|
70
|
+
def do_after_load
|
71
|
+
self.class.after_load_invoked('some_attr' => some_attr)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
should 'invoke the after_load callback' do
|
77
|
+
@class.expects(:after_load_invoked).with(@source_values)
|
78
|
+
@class.find(@key)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'destroy' do
|
83
|
+
should 'invoke the before_destroy and after_destroy callbacks on an existing row' do
|
84
|
+
@instance.stubs(:new_record?).returns(false)
|
85
|
+
@instance.expects(:do_before_destroy).once.in_sequence(@sequence).returns(true)
|
86
|
+
@class.expects(:delete).with(@instance.key).once.in_sequence(@sequence)
|
87
|
+
@instance.expects(:do_after_destroy).once.in_sequence(@sequence).returns(true)
|
88
|
+
@instance.destroy
|
89
|
+
end
|
90
|
+
|
91
|
+
should 'not invoke callbacks on a new row' do
|
92
|
+
@instance.stubs(:new_record?).returns(true)
|
93
|
+
@instance.expects(:do_before_destroy).never.in_sequence(@sequence)
|
94
|
+
@class.expects(:delete).with(@instance.key).never.in_sequence(@sequence)
|
95
|
+
@instance.expects(:do_after_destroy).never.in_sequence(@sequence)
|
96
|
+
@instance.destroy
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class IdentityTest < Test::Unit::TestCase
|
4
|
+
context 'CassandraWrapper::Base' do
|
5
|
+
setup do
|
6
|
+
@class = Class.new(CassandraMapper::Base)
|
7
|
+
@values = {:a => 'Aa', :b => 'Bb', :c => 'Cc'}
|
8
|
+
@values.keys.each {|key| @class.maps key}
|
9
|
+
@instance = @class.new(@values)
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'key attribute declaration' do
|
13
|
+
should 'be possible with the :key class method' do
|
14
|
+
@class.key :a
|
15
|
+
assert_equal :a, @class.key
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'determine the :key value on an instance' do
|
19
|
+
@class.key :a
|
20
|
+
assert_equal @values[:a], @instance.key
|
21
|
+
@class.key :b
|
22
|
+
assert_equal @values[:b], @instance.key
|
23
|
+
end
|
24
|
+
|
25
|
+
should 'be overriddable with an instance method' do
|
26
|
+
@class.key :a
|
27
|
+
@class.module_eval do
|
28
|
+
def key
|
29
|
+
"#{a}-#{b}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
assert_equal "#{@values[:a]}-#{@values[:b]}", @instance.key
|
33
|
+
end
|
34
|
+
|
35
|
+
should 'result in default of :key if left undeclared' do
|
36
|
+
assert_equal :key, @class.key
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
should 'default new_record? to true' do
|
41
|
+
assert_equal true, @instance.new_record?
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'allow specification of new_record flag via assignment' do
|
45
|
+
@instance.new_record = false
|
46
|
+
assert_equal false, @instance.new_record?
|
47
|
+
@instance.new_record = true
|
48
|
+
assert_equal true, @instance.new_record?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,406 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
class IndexingTest < Test::Unit::TestCase
|
3
|
+
context 'A CassandraMapper::Index' do
|
4
|
+
setup do
|
5
|
+
@class = CassandraMapper::Index
|
6
|
+
end
|
7
|
+
|
8
|
+
context 'with defaults' do
|
9
|
+
setup do
|
10
|
+
@instance = @class.new
|
11
|
+
end
|
12
|
+
|
13
|
+
should 'have a nil :indexed_class attribute' do
|
14
|
+
assert_equal nil, @instance.indexed_class
|
15
|
+
end
|
16
|
+
|
17
|
+
should 'have a nil :source attribute' do
|
18
|
+
assert_equal nil, @instance.source
|
19
|
+
end
|
20
|
+
|
21
|
+
should 'have a nil :column_family attribute' do
|
22
|
+
assert_equal nil, @instance.column_family
|
23
|
+
end
|
24
|
+
|
25
|
+
should 'have :key for :indexed_identifier' do
|
26
|
+
assert_equal :key, @instance.indexed_identifier
|
27
|
+
end
|
28
|
+
|
29
|
+
should 'have nil for :name attribute' do
|
30
|
+
assert_equal nil, @instance.name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'with constructor options' do
|
35
|
+
setup do
|
36
|
+
@options = {:column_family => :SomeIndex,
|
37
|
+
:indexed_class => Class.new,
|
38
|
+
:source => :to_be_indexed,
|
39
|
+
:indexed_identifier => :index_column,
|
40
|
+
:name => :some_index_name}
|
41
|
+
@instance = @class.new(@options)
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'get :indexed_class attribute' do
|
45
|
+
assert_equal @options[:indexed_class], @instance.indexed_class
|
46
|
+
end
|
47
|
+
|
48
|
+
should 'get :source attribute' do
|
49
|
+
assert_equal @options[:source], @instance.source
|
50
|
+
end
|
51
|
+
|
52
|
+
should 'get :column_family attribute' do
|
53
|
+
assert_equal @options[:column_family], @instance.column_family
|
54
|
+
end
|
55
|
+
|
56
|
+
should 'get :indexed_identifier attribute' do
|
57
|
+
assert_equal @options[:indexed_identifier], @instance.indexed_identifier
|
58
|
+
end
|
59
|
+
|
60
|
+
should 'get :name attribute' do
|
61
|
+
assert_equal @options[:name], @instance.name
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'retrieve :source_for(instance) from instance :source' do
|
66
|
+
instance = @class.new(:source => :some_source)
|
67
|
+
object = mock('object to be indexed')
|
68
|
+
object.expects(:some_source).with.returns('my source')
|
69
|
+
assert_equal 'my source', instance.source_for(object)
|
70
|
+
end
|
71
|
+
|
72
|
+
should 'retrieve :indexed_identifier_for(instance) from instance :indexed_identifier' do
|
73
|
+
instance = @class.new(:indexed_identifier => :some_identifier)
|
74
|
+
object = mock('object to be indexed')
|
75
|
+
object.expects(:some_identifier).with.returns('my identifier')
|
76
|
+
assert_equal 'my identifier', instance.indexed_identifier_for(object)
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'indexing method' do
|
80
|
+
setup do
|
81
|
+
@name = :this_index_name
|
82
|
+
@column_family = stub('column_family')
|
83
|
+
@source = :index_source
|
84
|
+
@indexed_class = Class.new(CassandraMapper::Base)
|
85
|
+
@target_object = @indexed_class.new
|
86
|
+
@indexed_identifier = :index_identifier
|
87
|
+
@instance = @class.new(:name => @name,
|
88
|
+
:indexed_class => @indexed_class,
|
89
|
+
:column_family => @column_family,
|
90
|
+
:source => @source,
|
91
|
+
:indexed_identifier => @indexed_identifier)
|
92
|
+
@client = stub('cassandra_client')
|
93
|
+
@target_object.stubs(:connection).returns(@client)
|
94
|
+
@target_object.stubs(:key).with.returns(@object_key = 'some_object_identifier')
|
95
|
+
@target_object.stubs(@indexed_identifier).with.returns(@object_identifier = 'some_object_sortable_identifier')
|
96
|
+
@target_object_state = stub('target_object_index_state')
|
97
|
+
@target_object.stubs(@name).returns(@target_object_state)
|
98
|
+
end
|
99
|
+
|
100
|
+
context ':create' do
|
101
|
+
should 'insert the target source as key to the column family, with key as value' do
|
102
|
+
@instance.expects(:source_for).with(@target_object).returns(source_value = 'some_source_value')
|
103
|
+
@client.expects(:insert).with(@column_family, source_value, {@object_identifier => @object_key})
|
104
|
+
@target_object_state.expects(:source_value=).with(source_value)
|
105
|
+
@target_object_state.expects(:identifier_value=).with(@object_identifier)
|
106
|
+
@instance.create(@target_object)
|
107
|
+
end
|
108
|
+
|
109
|
+
should 'do nothing if the current index source is nil' do
|
110
|
+
@instance.expects(:source_for).with(@target_object).returns(nil)
|
111
|
+
@client.expects(:insert).never
|
112
|
+
@instance.create(@target_object)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context ':remove' do
|
117
|
+
should "remove the target object's old identifier value from the row at the old source" do
|
118
|
+
# use sequences to work around naming conflicts
|
119
|
+
@target_object_state.stubs(:source_value).with.returns(source_value = 'some_old_source_value')
|
120
|
+
@target_object_state.stubs(:identifier_value).with.returns(identifier_value = 'some_old_identifier_value')
|
121
|
+
@target_object_state.expects(:source_value=).with(nil)
|
122
|
+
@target_object_state.expects(:identifier_value=).with(nil)
|
123
|
+
@client.expects(:remove).with(@column_family, source_value, identifier_value)
|
124
|
+
@instance.remove(@target_object)
|
125
|
+
end
|
126
|
+
|
127
|
+
should 'do nothing if the old identifier value is nil' do
|
128
|
+
@target_object_state.stubs(:source_value).with.returns('some_old_source_value')
|
129
|
+
@target_object_state.expects(:identifier_value).with.returns(nil)
|
130
|
+
@target_object_state.expects(:source_value=).never
|
131
|
+
@target_object_state.expects(:identifier_value=).never
|
132
|
+
@client.expects(:remove).never
|
133
|
+
@instance.remove(@target_object)
|
134
|
+
end
|
135
|
+
|
136
|
+
should 'do nothing if the old source value is nil' do
|
137
|
+
@target_object_state.expects(:source_value).with.returns(nil)
|
138
|
+
@target_object_state.stubs(:identifier_value).with.returns('some_old_identifier_value')
|
139
|
+
@target_object_state.expects(:source_value=).never
|
140
|
+
@target_object_state.expects(:identifier_value=).never
|
141
|
+
@client.expects(:remove).never
|
142
|
+
@instance.remove(@target_object)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context ':update' do
|
147
|
+
should 'perform a :create and :remove if the source values differ' do
|
148
|
+
@target_object_state.stubs(:source_value).with.returns('some_old_source_value')
|
149
|
+
@instance.expects(:source_for).with(@target_object).returns('some_source_value').at_least_once
|
150
|
+
@target_object_state.stubs(:identifier_value).with.returns(@object_identifier)
|
151
|
+
seq = sequence('operations')
|
152
|
+
@instance.expects(:remove).with(@target_object).in_sequence(seq)
|
153
|
+
@instance.expects(:create).with(@target_object).in_sequence(seq)
|
154
|
+
@instance.update(@target_object)
|
155
|
+
end
|
156
|
+
|
157
|
+
should 'perform a :create and :remove if identifier values differ' do
|
158
|
+
@target_object_state.stubs(:source_value).with.returns('some_source_value')
|
159
|
+
@instance.stubs(:source_for).with(@target_object).returns('some_source_value')
|
160
|
+
@target_object_state.stubs(:identifier_value).with.returns('some_old_identifier_value')
|
161
|
+
@instance.expects(:indexed_identifier_for).with(@target_object).returns('some new identifier value').at_least_once
|
162
|
+
seq = sequence('operations')
|
163
|
+
@instance.expects(:remove).with(@target_object).in_sequence(seq)
|
164
|
+
@instance.expects(:create).with(@target_object).in_sequence(seq)
|
165
|
+
@instance.update(@target_object)
|
166
|
+
end
|
167
|
+
|
168
|
+
should 'do nothing if the values are unchanged' do
|
169
|
+
@target_object_state.expects(:source_value).with.returns('some_source_value')
|
170
|
+
@instance.expects(:source_for).with(@target_object).returns('some_source_value').at_least_once
|
171
|
+
@target_object_state.expects(:identifier_value).with.returns(@object_identifier)
|
172
|
+
@instance.expects(:indexed_identifier_for).with(@target_object).returns(@object_identifier).at_least_once
|
173
|
+
@instance.expects(:remove).never
|
174
|
+
@instance.expects(:create).never
|
175
|
+
@instance.update(@target_object)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context 'read method' do
|
181
|
+
setup do
|
182
|
+
@indexed_class = Class.new(CassandraMapper::Base) do
|
183
|
+
maps :key
|
184
|
+
maps :some_source_field
|
185
|
+
end
|
186
|
+
@column_family = :MyIndexes
|
187
|
+
@source_attrib = :some_source_field
|
188
|
+
@instance = @class.new(:indexed_class => @indexed_class,
|
189
|
+
:column_family => @column_family,
|
190
|
+
:source => @source_attrib)
|
191
|
+
@connection = stub(:connection)
|
192
|
+
@indexed_class.stubs(:connection).returns(@connection)
|
193
|
+
# the order reflects insert order, so we need to be very explicit here
|
194
|
+
@expected = Cassandra::OrderedHash.new()
|
195
|
+
@expected['0000-id1'] = 'id1'
|
196
|
+
@expected['0001-id2'] = 'id2'
|
197
|
+
@sorted_ids = ['id1', 'id2']
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'get' do
|
201
|
+
context 'for single indexed value' do
|
202
|
+
setup do
|
203
|
+
@key = 'this indexed value'
|
204
|
+
end
|
205
|
+
|
206
|
+
should 'perform Cassandra get for the given index value and return raw result' do
|
207
|
+
@connection.expects(:get).with(@column_family, @key, {}).returns(@expected)
|
208
|
+
result = @instance.get(@key)
|
209
|
+
assert_equal @expected, result
|
210
|
+
end
|
211
|
+
|
212
|
+
should 'pass any options along to the Cassandra.get invocation' do
|
213
|
+
options = {:count => 2, :start => 'foo'}
|
214
|
+
@connection.expects(:get).with(@column_family, @key, options).returns(@expected)
|
215
|
+
@instance.get(@key, options)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'for multiple indexed values' do
|
220
|
+
setup do
|
221
|
+
@keys = ['this indexed value', 'that indexed value', 'mine', 'yours']
|
222
|
+
@client_result = Cassandra::OrderedHash.new()
|
223
|
+
# the order reflects insert order, so we need to be explicit
|
224
|
+
@client_result['this indexed value'] = {'0000-id1' => 'id1'}
|
225
|
+
@client_result['that indexed value'] = {'0001-id2' => 'id2'}
|
226
|
+
@client_result['mine'] = {'aaaa-id1' => 'id1'}
|
227
|
+
@client_result['yours'] = {'bbbb-id2' => 'id2'}
|
228
|
+
@expected.merge!(@client_result['mine'])
|
229
|
+
@expected.merge!(@client_result['yours'])
|
230
|
+
end
|
231
|
+
|
232
|
+
should 'perform Cassandra.multi_get for multiple index values and return merged result' do
|
233
|
+
@connection.expects(:multi_get).with(@column_family, @keys, {}).returns(@client_result)
|
234
|
+
result = @instance.get(@keys)
|
235
|
+
assert_equal @expected, result
|
236
|
+
end
|
237
|
+
|
238
|
+
should 'pass any options along to underlying Cassandra.multi_get' do
|
239
|
+
options = {:count => 4, :finish => 'zzzzzyyyyzzzz'}
|
240
|
+
@connection.expects(:multi_get).with(@column_family, @keys, options).returns(@client_result)
|
241
|
+
@instance.get(@keys, options)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'keys' do
|
247
|
+
should 'retrieve data from :get and return identifiers based on result indexed id order' do
|
248
|
+
value = 'some value'
|
249
|
+
@instance.expects(:get).with(value, {}).returns(@expected)
|
250
|
+
result = @instance.keys(value)
|
251
|
+
assert_equal @sorted_ids, result
|
252
|
+
end
|
253
|
+
|
254
|
+
should 'collapse redundant identifiers down and preserve earliest order' do
|
255
|
+
value = 'some value'
|
256
|
+
@expected["aaaa-#{@sorted_ids[0]}"] = @sorted_ids[0]
|
257
|
+
@expected["bbbb-#{@sorted_ids[1]}"] = @sorted_ids[1]
|
258
|
+
@instance.expects(:get).with(value, {}).returns(@expected)
|
259
|
+
result = @instance.keys(value)
|
260
|
+
assert_equal @sorted_ids, result
|
261
|
+
end
|
262
|
+
|
263
|
+
should 'pass options through to :get' do
|
264
|
+
options = {:this => :that, :mine => :not_yours}
|
265
|
+
value = 'foo'
|
266
|
+
@instance.expects(:get).with(value, options).returns(@expected)
|
267
|
+
@instance.keys(value, options)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context 'objects' do
|
272
|
+
setup do
|
273
|
+
@expected_objects = @sorted_ids.collect {|id| @indexed_class.new(:key => id)}
|
274
|
+
end
|
275
|
+
|
276
|
+
should 'return results of a model class :find call with :keys as its arguments' do
|
277
|
+
@indexed_class.expects(:find).with(@sorted_ids, {:allow_missing => true}).returns(@expected_objects)
|
278
|
+
@instance.expects(:keys).with(value = 'foo', {}).returns(@sorted_ids)
|
279
|
+
result = @instance.objects(value)
|
280
|
+
assert_equal @expected_objects, result
|
281
|
+
end
|
282
|
+
|
283
|
+
should 'not call :find if :keys is empty, and return an empty array' do
|
284
|
+
@indexed_class.expects(:find).never
|
285
|
+
@instance.expects(:keys).with(value = 'foo', {}).returns([])
|
286
|
+
result = @instance.objects(value)
|
287
|
+
assert_equal [], result
|
288
|
+
end
|
289
|
+
|
290
|
+
should 'pass options through to :keys' do
|
291
|
+
@instance.expects(:keys).with(value = 'foo', options = {:this => :and, :that => :dude}).returns(@sorted_ids)
|
292
|
+
@indexed_class.expects(:find).with(@sorted_ids, {:allow_missing => true}).returns(@expected_objects)
|
293
|
+
@instance.objects(value, options)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
context 'A Cassandra::Base-derived class' do
|
300
|
+
setup do
|
301
|
+
@class = Class.new(CassandraMapper::Base)
|
302
|
+
end
|
303
|
+
|
304
|
+
context 'has_index method' do
|
305
|
+
should 'create a class accessor for the named index' do
|
306
|
+
assert_equal true, ! @class.respond_to?(:this_index)
|
307
|
+
@class.has_index :this_index
|
308
|
+
assert_equal true, @class.respond_to?(:this_index)
|
309
|
+
end
|
310
|
+
|
311
|
+
should 'set the :name attribute on the resulting index object' do
|
312
|
+
@class.has_index :this_index
|
313
|
+
assert_equal :this_index, @class.this_index.name
|
314
|
+
end
|
315
|
+
|
316
|
+
should 'determine class of index from the :class option' do
|
317
|
+
subclass = Class.new(CassandraMapper::Index)
|
318
|
+
@class.has_index :this_index, :class => subclass
|
319
|
+
assert_equal true, subclass === @class.this_index
|
320
|
+
end
|
321
|
+
|
322
|
+
should 'default class of index to CassandraMapper::Index' do
|
323
|
+
@class.has_index :this_index
|
324
|
+
assert_equal true, CassandraMapper::Index === @class.this_index
|
325
|
+
end
|
326
|
+
|
327
|
+
should 'pass all options, with :indexed_class mapped to indexed class, to the constructor' do
|
328
|
+
options = {:a => 'a', :b => 'b', :c => 'c'}
|
329
|
+
subclass = Class.new(CassandraMapper::Index)
|
330
|
+
subclass.expects(:new).with(options.merge(:indexed_class => @class,
|
331
|
+
:name => :this_index)).returns(CassandraMapper::Index.new(options.merge(:indexed_class => @class)))
|
332
|
+
@class.has_index :this_index, options.merge(:class => subclass)
|
333
|
+
end
|
334
|
+
|
335
|
+
context 'given a block' do
|
336
|
+
should 'evaluate the block in the context of the new index object' do
|
337
|
+
received = []
|
338
|
+
@class.has_index :this_index do
|
339
|
+
received << self
|
340
|
+
end
|
341
|
+
assert_equal [@class.this_index], received
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
context 'instance' do
|
347
|
+
setup do
|
348
|
+
@instance = @class.new
|
349
|
+
end
|
350
|
+
|
351
|
+
context 'with class that has_index :foo' do
|
352
|
+
setup do
|
353
|
+
@class.has_index :foo
|
354
|
+
@class.maps :some_attr
|
355
|
+
@class.maps :id
|
356
|
+
@class.key :id
|
357
|
+
end
|
358
|
+
|
359
|
+
should 'have an attribute :foo of type CassandraMapper::Index::State' do
|
360
|
+
assert_equal CassandraMapper::Index::State, @instance.foo.class
|
361
|
+
end
|
362
|
+
|
363
|
+
should 'have nil index state values for :foo state object' do
|
364
|
+
assert_equal nil, @instance.foo.source_value
|
365
|
+
assert_equal nil, @instance.foo.identifier_value
|
366
|
+
end
|
367
|
+
|
368
|
+
should 'receive proper state_value and identifier_value values after :loaded' do
|
369
|
+
@class.foo.expects(:source_for).with(@instance).returns(source = 'some_source_value')
|
370
|
+
@class.foo.expects(:indexed_identifier_for).with(@instance).returns(key = 'some_key')
|
371
|
+
@instance.loaded!
|
372
|
+
assert_equal source, @instance.foo.source_value
|
373
|
+
assert_equal key, @instance.foo.identifier_value
|
374
|
+
end
|
375
|
+
|
376
|
+
should 'create the index upon saving a new instance' do
|
377
|
+
@instance.stubs(:new_record?).returns(true)
|
378
|
+
@instance.some_attr = 'blah'
|
379
|
+
@instance.id = 'foo'
|
380
|
+
@class.foo.expects(:create).with(@instance)
|
381
|
+
@instance.connection.expects(:insert)
|
382
|
+
@instance.save
|
383
|
+
end
|
384
|
+
|
385
|
+
should 'update the index upon saving an existing instance' do
|
386
|
+
@instance.stubs(:new_record?).returns(false)
|
387
|
+
@instance.some_attr = 'blah'
|
388
|
+
@instance.id = 'foo'
|
389
|
+
@class.foo.expects(:update).with(@instance)
|
390
|
+
@instance.connection.expects(:insert)
|
391
|
+
@instance.save
|
392
|
+
end
|
393
|
+
|
394
|
+
should 'remove from the index upon destroying an existing instance' do
|
395
|
+
@instance.stubs(:new_record?).returns(false)
|
396
|
+
@instance.stubs(:freeze)
|
397
|
+
@instance.some_attr = 'blah'
|
398
|
+
@instance.id = 'foo'
|
399
|
+
@class.foo.expects(:remove).with(@instance)
|
400
|
+
@instance.connection.expects(:remove)
|
401
|
+
@instance.destroy
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|