gotime-cassandra_object 0.6.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 (54) hide show
  1. data/CHANGELOG +3 -0
  2. data/Gemfile +14 -0
  3. data/Gemfile.lock +42 -0
  4. data/LICENSE +13 -0
  5. data/README.markdown +79 -0
  6. data/Rakefile +74 -0
  7. data/TODO +2 -0
  8. data/VERSION +1 -0
  9. data/gotime-cassandra_object.gemspec +134 -0
  10. data/lib/cassandra_object.rb +13 -0
  11. data/lib/cassandra_object/associations.rb +35 -0
  12. data/lib/cassandra_object/associations/one_to_many.rb +136 -0
  13. data/lib/cassandra_object/associations/one_to_one.rb +77 -0
  14. data/lib/cassandra_object/attributes.rb +93 -0
  15. data/lib/cassandra_object/base.rb +97 -0
  16. data/lib/cassandra_object/callbacks.rb +10 -0
  17. data/lib/cassandra_object/collection.rb +8 -0
  18. data/lib/cassandra_object/cursor.rb +86 -0
  19. data/lib/cassandra_object/dirty.rb +27 -0
  20. data/lib/cassandra_object/identity.rb +61 -0
  21. data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
  22. data/lib/cassandra_object/identity/key.rb +20 -0
  23. data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
  24. data/lib/cassandra_object/identity/uuid_key_factory.rb +37 -0
  25. data/lib/cassandra_object/indexes.rb +129 -0
  26. data/lib/cassandra_object/log_subscriber.rb +17 -0
  27. data/lib/cassandra_object/migrations.rb +72 -0
  28. data/lib/cassandra_object/mocking.rb +15 -0
  29. data/lib/cassandra_object/persistence.rb +195 -0
  30. data/lib/cassandra_object/serialization.rb +6 -0
  31. data/lib/cassandra_object/type_registration.rb +7 -0
  32. data/lib/cassandra_object/types.rb +128 -0
  33. data/lib/cassandra_object/validation.rb +49 -0
  34. data/test/basic_scenarios_test.rb +243 -0
  35. data/test/callbacks_test.rb +19 -0
  36. data/test/config/cassandra.in.sh +53 -0
  37. data/test/config/log4j.properties +38 -0
  38. data/test/config/storage-conf.xml +221 -0
  39. data/test/connection.rb +25 -0
  40. data/test/cursor_test.rb +66 -0
  41. data/test/dirty_test.rb +34 -0
  42. data/test/fixture_models.rb +90 -0
  43. data/test/identity/natural_key_factory_test.rb +94 -0
  44. data/test/index_test.rb +69 -0
  45. data/test/legacy/test_helper.rb +18 -0
  46. data/test/migration_test.rb +21 -0
  47. data/test/one_to_many_associations_test.rb +163 -0
  48. data/test/test_case.rb +28 -0
  49. data/test/test_helper.rb +16 -0
  50. data/test/time_test.rb +32 -0
  51. data/test/types_test.rb +252 -0
  52. data/test/validation_test.rb +25 -0
  53. data/test/z_mock_test.rb +36 -0
  54. metadata +243 -0
@@ -0,0 +1,53 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+
18
+ cassandra_home=`dirname $0`/..
19
+
20
+ # The directory where Cassandra's configs live (required)
21
+ CASSANDRA_CONF=`pwd`/../test/config
22
+
23
+ # This can be the path to a jar file, or a directory containing the
24
+ # compiled classes. NOTE: This isn't needed by the startup script,
25
+ # it's just used here in constructing the classpath.
26
+ cassandra_bin=$cassandra_home/build/classes
27
+ #cassandra_bin=$cassandra_home/build/cassandra.jar
28
+
29
+ # The java classpath (required)
30
+ CLASSPATH=$CASSANDRA_CONF:$cassandra_bin
31
+
32
+ for jar in $cassandra_home/lib/*.jar; do
33
+ CLASSPATH=$CLASSPATH:$jar
34
+ done
35
+
36
+ # Arguments to pass to the JVM
37
+ JVM_OPTS=" \
38
+ -ea \
39
+ -Xdebug \
40
+ -Xrunjdwp:transport=dt_socket,server=y,address=8888,suspend=n \
41
+ -Xms128M \
42
+ -Xmx1G \
43
+ -XX:SurvivorRatio=8 \
44
+ -XX:TargetSurvivorRatio=90 \
45
+ -XX:+AggressiveOpts \
46
+ -XX:+UseParNewGC \
47
+ -XX:+UseConcMarkSweepGC \
48
+ -XX:CMSInitiatingOccupancyFraction=1 \
49
+ -XX:+CMSParallelRemarkEnabled \
50
+ -XX:+HeapDumpOnOutOfMemoryError \
51
+ -Dcom.sun.management.jmxremote.port=8080 \
52
+ -Dcom.sun.management.jmxremote.ssl=false \
53
+ -Dcom.sun.management.jmxremote.authenticate=false"
@@ -0,0 +1,38 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ # for production, you should probably set the root to INFO
18
+ # and the pattern to %c instead of %l. (%l is slower.)
19
+
20
+ # output messages into a rolling log file as well as stdout
21
+ log4j.rootLogger=DEBUG,stdout,R
22
+
23
+ # stdout
24
+ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
25
+ log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
26
+
27
+ # rolling log file ("system.log
28
+ log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
29
+ log4j.appender.R.DatePattern='.'yyyy-MM-dd-HH
30
+ log4j.appender.R.layout=org.apache.log4j.PatternLayout
31
+ log4j.appender.R.layout.ConversionPattern=%5p [%t] %d{ISO8601} %F (line %L) %m%n
32
+ # Edit the next line to point to your logs directory
33
+ log4j.appender.R.File=data/logs/system.log
34
+
35
+ # Application logging options
36
+ #log4j.logger.com.facebook=DEBUG
37
+ #log4j.logger.com.facebook.infrastructure.gms=DEBUG
38
+ #log4j.logger.com.facebook.infrastructure.db=DEBUG
@@ -0,0 +1,221 @@
1
+ <!--
2
+ ~ Licensed to the Apache Software Foundation (ASF) under one
3
+ ~ or more contributor license agreements. See the NOTICE file
4
+ ~ distributed with this work for additional information
5
+ ~ regarding copyright ownership. The ASF licenses this file
6
+ ~ to you under the Apache License, Version 2.0 (the
7
+ ~ "License"); you may not use this file except in compliance
8
+ ~ with the License. You may obtain a copy of the License at
9
+ ~
10
+ ~ http:/www.apache.org/licenses/LICENSE-2.0
11
+ ~
12
+ ~ Unless required by applicable law or agreed to in writing,
13
+ ~ software distributed under the License is distributed on an
14
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ ~ KIND, either express or implied. See the License for the
16
+ ~ specific language governing permissions and limitations
17
+ ~ under the License.
18
+ -->
19
+ <Storage>
20
+ <!--======================================================================-->
21
+ <!-- Basic Configuration -->
22
+ <!--======================================================================-->
23
+ <ClusterName>Test</ClusterName>
24
+
25
+ <!-- Tables and ColumnFamilies
26
+ Think of a table as a namespace, not a relational table.
27
+ (ColumnFamilies are closer in meaning to those.)
28
+
29
+ There is an implicit table named 'system' for Cassandra internals.
30
+ -->
31
+ <Keyspaces>
32
+ <Keyspace Name="Twitter">
33
+ <KeysCachedFraction>0.01</KeysCachedFraction>
34
+ <ColumnFamily CompareWith="UTF8Type" Name="Users" />
35
+ <ColumnFamily CompareWith="UTF8Type" Name="UserAudits" />
36
+ <ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" Name="UserRelationships" />
37
+ <ColumnFamily CompareWith="UTF8Type" Name="Usernames" />
38
+ <ColumnFamily CompareWith="UTF8Type" Name="Statuses" />
39
+ <ColumnFamily CompareWith="UTF8Type" Name="StatusAudits" />
40
+ <ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" Name="StatusRelationships" />
41
+ </Keyspace>
42
+
43
+ <Keyspace Name="Multiblog">
44
+ <KeysCachedFraction>0.01</KeysCachedFraction>
45
+ <ColumnFamily CompareWith="UTF8Type" Name="Blogs"/>
46
+ <ColumnFamily CompareWith="UTF8Type" Name="Comments"/>
47
+ </Keyspace>
48
+
49
+ <Keyspace Name="CassandraObject">
50
+ <KeysCachedFraction>0.01</KeysCachedFraction>
51
+ <ColumnFamily CompareWith="UTF8Type" Name="Customers" />
52
+ <ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" Name="CustomerRelationships" />
53
+ <ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" Name="CustomersByLastName" />
54
+ <ColumnFamily CompareWith="UTF8Type" Name="Invoices" />
55
+ <ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" Name="InvoiceRelationships" />
56
+ <ColumnFamily CompareWith="UTF8Type" Name="InvoicesByNumber" />
57
+ <ColumnFamily CompareWith="UTF8Type" Name="Payments" />
58
+ </Keyspace>
59
+ </Keyspaces>
60
+
61
+ <!-- Partitioner: any IPartitioner may be used, including your own
62
+ as long as it is on the classpath. Out of the box,
63
+ Cassandra provides
64
+ org.apache.cassandra.dht.RandomPartitioner and
65
+ org.apache.cassandra.dht.OrderPreservingPartitioner.
66
+ Range queries require using OrderPreservingPartitioner or a subclass.
67
+
68
+ Achtung! Changing this parameter requires wiping your data directories,
69
+ since the partitioner can modify the sstable on-disk format.
70
+ -->
71
+ <Partitioner>org.apache.cassandra.dht.OrderPreservingPartitioner</Partitioner>
72
+
73
+ <!-- If you are using the OrderPreservingPartitioner and you know your key
74
+ distribution, you can specify the token for this node to use.
75
+ (Keys are sent to the node with the "closest" token, so distributing
76
+ your tokens equally along the key distribution space will spread
77
+ keys evenly across your cluster.) This setting is only checked the
78
+ first time a node is started.
79
+
80
+ This can also be useful with RandomPartitioner to force equal
81
+ spacing of tokens around the hash space, especially for
82
+ clusters with a small number of nodes. -->
83
+ <InitialToken></InitialToken>
84
+
85
+
86
+ <!-- EndPointSnitch: Setting this to the class that implements IEndPointSnitch
87
+ which will see if two endpoints are in the same data center or on the same rack.
88
+ Out of the box, Cassandra provides
89
+ org.apache.cassandra.locator.EndPointSnitch
90
+ -->
91
+ <EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch</EndPointSnitch>
92
+
93
+ <!-- Strategy: Setting this to the class that implements IReplicaPlacementStrategy
94
+ will change the way the node picker works.
95
+ Out of the box, Cassandra provides
96
+ org.apache.cassandra.locator.RackUnawareStrategy
97
+ org.apache.cassandra.locator.RackAwareStrategy
98
+ (place one replica in a different datacenter, and the
99
+ others on different racks in the same one.)
100
+ -->
101
+ <ReplicaPlacementStrategy>org.apache.cassandra.locator.RackUnawareStrategy</ReplicaPlacementStrategy>
102
+
103
+ <!-- Number of replicas of the data-->
104
+ <ReplicationFactor>1</ReplicationFactor>
105
+
106
+ <!-- Directories: Specify where Cassandra should store different data on disk
107
+ Keep the data disks and the CommitLog disks separate for best performance
108
+ -->
109
+ <CommitLogDirectory>data/commitlog</CommitLogDirectory>
110
+ <DataFileDirectories>
111
+ <DataFileDirectory>data/data</DataFileDirectory>
112
+ </DataFileDirectories>
113
+ <CalloutLocation>data/callouts</CalloutLocation>
114
+ <BootstrapFileDirectory>data/bootstrap</BootstrapFileDirectory>
115
+ <StagingFileDirectory>data/staging</StagingFileDirectory>
116
+
117
+ <!-- Addresses of hosts that are deemed contact points. Cassandra nodes use
118
+ this list of hosts to find each other and learn the topology of the ring.
119
+ You must change this if you are running multiple nodes!
120
+ -->
121
+ <Seeds>
122
+ <Seed>127.0.0.1</Seed>
123
+ </Seeds>
124
+
125
+
126
+ <!-- Miscellaneous -->
127
+
128
+ <!-- time to wait for a reply from other nodes before failing the command -->
129
+ <RpcTimeoutInMillis>5000</RpcTimeoutInMillis>
130
+ <!-- size to allow commitlog to grow to before creating a new segment -->
131
+ <CommitLogRotationThresholdInMB>128</CommitLogRotationThresholdInMB>
132
+
133
+
134
+ <!-- Local hosts and ports -->
135
+
136
+ <!-- Address to bind to and tell other nodes to connect to.
137
+ You _must_ change this if you want multiple nodes to be able
138
+ to communicate!
139
+
140
+ Leaving it blank leaves it up to InetAddress.getLocalHost().
141
+ This will always do the Right Thing *if* the node is properly
142
+ configured (hostname, name resolution, etc), and the Right
143
+ Thing is to use the address associated with the hostname (it
144
+ might not be). -->
145
+ <ListenAddress>localhost</ListenAddress>
146
+ <!-- TCP port, for commands and data -->
147
+ <StoragePort>7000</StoragePort>
148
+ <!-- UDP port, for membership communications (gossip) -->
149
+ <ControlPort>7001</ControlPort>
150
+
151
+ <!-- The address to bind the Thrift RPC service to. Unlike
152
+ ListenAddress above, you *can* specify 0.0.0.0 here if you want
153
+ Thrift to listen on all interfaces.
154
+
155
+ Leaving this blank has the same effect it does for ListenAddress,
156
+ (i.e. it will be based on the configured hostname of the node).
157
+ -->
158
+ <ThriftAddress>localhost</ThriftAddress>
159
+ <!-- Thrift RPC port (the port clients connect to). -->
160
+ <ThriftPort>9160</ThriftPort>
161
+
162
+
163
+ <!--======================================================================-->
164
+ <!-- Memory, Disk, and Performance -->
165
+ <!--======================================================================-->
166
+
167
+ <!-- Add column indexes to a row after its contents reach this size -->
168
+ <ColumnIndexSizeInKB>256</ColumnIndexSizeInKB>
169
+
170
+ <!--
171
+ The maximum amount of data to store in memory before flushing to
172
+ disk. Note: There is one memtable per column family, and this threshold
173
+ is based solely on the amount of data stored, not actual heap memory
174
+ usage (there is some overhead in indexing the columns).
175
+ -->
176
+ <MemtableSizeInMB>32</MemtableSizeInMB>
177
+
178
+ <!--
179
+ The maximum number of columns in millions to store in memory
180
+ before flushing to disk. This is also a per-memtable setting.
181
+ Use with MemtableSizeInMB to tune memory usage.
182
+ -->
183
+ <MemtableObjectCountInMillions>0.01</MemtableObjectCountInMillions>
184
+
185
+ <!-- Unlike most systems, in Cassandra writes are faster than
186
+ reads, so you can afford more of those in parallel.
187
+ A good rule of thumb is 2 concurrent reads per processor core.
188
+ You especially want more concurrentwrites if you are using
189
+ CommitLogSync + CommitLogSyncDelay. -->
190
+ <ConcurrentReads>8</ConcurrentReads>
191
+ <ConcurrentWrites>32</ConcurrentWrites>
192
+
193
+ <!-- Turn on CommitLogSync to improve durability.
194
+ When enabled, Cassandra won't ack writes until the commit log
195
+ has been synced to disk. This is less necessary in Cassandra
196
+ than in traditional databases since replication reduces the
197
+ odds of losing data from a failure after writing the log
198
+ entry but before it actually reaches the disk.
199
+ -->
200
+ <CommitLogSync>false</CommitLogSync>
201
+ <!-- Delay (in microseconds) during which additional commit log
202
+ entries may be written before fsync. This will increase
203
+ latency slightly, but can vastly improve throughput where
204
+ there are many writers. Set to zero to disable
205
+ (each entry will be synced individually).
206
+ Reasonable values range from a minimal 100 to even 10000
207
+ if throughput matters more than latency. (10000us = 10ms
208
+ write latency isn't even that bad by traditional db
209
+ standards.)
210
+ -->
211
+ <CommitLogSyncDelay>1000</CommitLogSyncDelay>
212
+
213
+
214
+ <!-- Time to wait before garbage-collection deletion markers.
215
+ Set this to a large enough value that you are confident
216
+ that the deletion marker will be propagated to all replicas
217
+ by the time this many seconds has elapsed, even in the
218
+ face of hardware failures. The default value is ten days.
219
+ -->
220
+ <GCGraceSeconds>864000</GCGraceSeconds>
221
+ </Storage>
@@ -0,0 +1,25 @@
1
+ ENV['CASSANDRA'] ||= "/usr/local/cassandra/bin/cassandra"
2
+
3
+ if ENV['CASSANDRA_REQUIRED']
4
+ tmp = File.expand_path(File.join(File.dirname(__FILE__), '..', 'tmp'))
5
+ config = File.expand_path(File.join(File.dirname(__FILE__), 'config'))
6
+
7
+ $pid = fork {
8
+ Dir.chdir(tmp)
9
+ puts "CASSANDRA_INCLUDE=#{config}/cassandra.in.sh #{ENV['CASSANDRA']} -f"
10
+ }
11
+
12
+ # Wait for cassandra to boot
13
+ sleep 3
14
+ end
15
+
16
+ puts "Connecting..."
17
+ CassandraObject::Base.establish_connection "CassandraObject"
18
+
19
+ if defined?($pid)
20
+ at_exit do
21
+ puts "Shutting down Cassandra..."
22
+ Process.kill('INT', $pid)
23
+ Process.wait($pid)
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ require 'test_helper'
2
+
3
+ class CursorTest < CassandraObjectTestCase
4
+
5
+ context "A Cursor working with a Super Column with mixed valid / invalid keys" do
6
+ setup do
7
+ @customer = Customer.create :first_name => "Michael",
8
+ :last_name => "Koziarski",
9
+ :date_of_birth => Date.parse("1980/08/15")
10
+
11
+ @old = mock_invoice.tap {|i| @customer.invoices << i }
12
+
13
+ @to_die = mock_invoice.tap {|i| @customer.invoices << i }
14
+
15
+ @new = mock_invoice.tap {|i| @customer.invoices << i }
16
+
17
+ assert_ordered [@new.key, @to_die.key, @old.key],
18
+ association_keys_in_cassandra
19
+
20
+ Invoice.remove(@to_die.key)
21
+ end
22
+
23
+ context "starting at the beginning" do
24
+ setup do
25
+ @cursor = invoices_cursor(:reversed=>true)
26
+ end
27
+
28
+ should "leave values alone it doesn't scroll past" do
29
+ assert_equal [@new], @cursor.find(1)
30
+
31
+ assert_ordered [@new.key, @to_die.key, @old.key],
32
+ association_keys_in_cassandra
33
+ end
34
+
35
+ should "clean up when it hits a missing record" do
36
+ assert_equal [@new, @old], @cursor.find(2)
37
+ assert_ordered [@new.key, @old.key],
38
+ association_keys_in_cassandra
39
+ end
40
+ end
41
+
42
+ context "starting at an offset" do
43
+ setup do
44
+ start_after = invoices_cursor(:reversed=>true).find(1).last_column_name
45
+ @cursor = invoices_cursor(:start_after=>start_after, :reversed=>true)
46
+ end
47
+
48
+ should "clean up when it hits a missing record" do
49
+ assert_equal [@old.key], @cursor.find(1).map(&:key), "Wasn't expecting #{@new.key.inspect}"
50
+ assert_ordered [@new.key, @old.key],
51
+ association_keys_in_cassandra
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+
58
+ def association_keys_in_cassandra
59
+ res = Customer.connection.get(Customer.associations[:invoices].column_family, @customer.key.to_s, "invoices", :reversed=>true)
60
+ res.values
61
+ end
62
+
63
+ def invoices_cursor(options = {})
64
+ CassandraObject::Cursor.new(Invoice, Customer.associations[:invoices].column_family, @customer.key, "invoices", options)
65
+ end
66
+ end
@@ -0,0 +1,34 @@
1
+ require 'test_helper'
2
+
3
+ class DirtyTest < CassandraObjectTestCase
4
+ def setup
5
+ super
6
+
7
+ @customer = Customer.create :first_name => "Michael",
8
+ :last_name => "Koziarski",
9
+ :date_of_birth => Date.parse("1980/08/15")
10
+ @customer_key = @customer.key
11
+ assert @customer.valid?, @customer.errors.full_messages.to_sentence
12
+ end
13
+
14
+ test "a new object can be retrieved by key" do
15
+ assert_equal "Michael", @customer.first_name
16
+ assert !@customer.changed?
17
+ assert_equal [], @customer.changed
18
+ assert_equal({}, @customer.changes)
19
+
20
+ @customer.first_name = "Josh"
21
+ assert_equal "Josh", @customer.first_name
22
+
23
+ assert @customer.changed?
24
+ assert_equal ["first_name"], @customer.changed
25
+ assert_equal({"first_name" => ["Michael", "Josh"]}, @customer.changes)
26
+
27
+ assert @customer.first_name_changed?
28
+ assert_equal ["Michael", "Josh"], @customer.first_name_change
29
+ assert_equal "Michael", @customer.first_name_was
30
+
31
+ @customer.reset_first_name!
32
+ assert_equal "Michael", @customer.first_name
33
+ end
34
+ end
@@ -0,0 +1,90 @@
1
+ module ReverseStorage
2
+ def encode(str)
3
+ str.reverse
4
+ end
5
+ module_function :encode
6
+
7
+ def decode(str)
8
+ str.reverse
9
+ end
10
+ module_function :decode
11
+ end
12
+
13
+
14
+ class Customer < CassandraObject::Base
15
+ attribute :first_name, :type => :string
16
+ attribute :last_name, :type => :string
17
+ attribute :date_of_birth, :type => :date
18
+ attribute :preferences, :type => :hash
19
+ attribute :custom_storage, :type => String, :converter=>ReverseStorage
20
+
21
+ validate :should_be_cool
22
+ validates_presence_of :last_name
23
+
24
+ after_create :set_after_create_called
25
+
26
+ key :uuid
27
+
28
+ index :last_name, :reversed=>true
29
+
30
+ association :invoices, :unique=>false, :inverse_of=>:customer, :reversed=>true
31
+ association :paid_invoices, :unique=>false, :class_name=>'Invoice'
32
+
33
+ def after_create_called?
34
+ @after_create_called
35
+ end
36
+
37
+ def set_after_create_called
38
+ @after_create_called = true
39
+ end
40
+
41
+ private
42
+
43
+ def should_be_cool
44
+ unless ["Michael", "Anika", "Evan", "Tom"].include?(first_name)
45
+ errors.add(:first_name, "must be that of a cool person")
46
+ end
47
+ end
48
+ end
49
+
50
+ class Invoice < CassandraObject::Base
51
+ attribute :number, :type=>:integer
52
+ attribute :total, :type=>:float
53
+ attribute :gst_number, :type=>:string
54
+
55
+ index :number, :unique=>true
56
+
57
+ association :customer, :unique=>true, :inverse_of=>:invoices
58
+
59
+ migrate 1 do |attrs|
60
+ attrs["total"] ||= (rand(2000) / 100.0).to_s
61
+ end
62
+
63
+ migrate 2 do |attrs|
64
+ attrs["gst_number"] = "66-666-666"
65
+ end
66
+
67
+ key :uuid
68
+ end
69
+
70
+ class Payment < CassandraObject::Base
71
+ attribute :reference_number, :type => :string
72
+ attribute :amount, :type => :integer
73
+
74
+ key :natural, :attributes => :reference_number
75
+ end
76
+
77
+ MockRecord = Struct.new(:key)
78
+
79
+ class Person < CassandraObject::Base
80
+ attribute :name, :type => :string
81
+ attribute :age, :type => :integer
82
+ end
83
+
84
+ class Appointment < CassandraObject::Base
85
+ attribute :title, :type => :string
86
+ attribute :start_time, :type => :time
87
+ attribute :end_time, :type => :time_with_zone, :allow_nil => true
88
+
89
+ key :natural, :attributes => :title
90
+ end