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.
- data/CHANGELOG +3 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +42 -0
- data/LICENSE +13 -0
- data/README.markdown +79 -0
- data/Rakefile +74 -0
- data/TODO +2 -0
- data/VERSION +1 -0
- data/gotime-cassandra_object.gemspec +134 -0
- data/lib/cassandra_object.rb +13 -0
- data/lib/cassandra_object/associations.rb +35 -0
- data/lib/cassandra_object/associations/one_to_many.rb +136 -0
- data/lib/cassandra_object/associations/one_to_one.rb +77 -0
- data/lib/cassandra_object/attributes.rb +93 -0
- data/lib/cassandra_object/base.rb +97 -0
- data/lib/cassandra_object/callbacks.rb +10 -0
- data/lib/cassandra_object/collection.rb +8 -0
- data/lib/cassandra_object/cursor.rb +86 -0
- data/lib/cassandra_object/dirty.rb +27 -0
- data/lib/cassandra_object/identity.rb +61 -0
- data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
- data/lib/cassandra_object/identity/key.rb +20 -0
- data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
- data/lib/cassandra_object/identity/uuid_key_factory.rb +37 -0
- data/lib/cassandra_object/indexes.rb +129 -0
- data/lib/cassandra_object/log_subscriber.rb +17 -0
- data/lib/cassandra_object/migrations.rb +72 -0
- data/lib/cassandra_object/mocking.rb +15 -0
- data/lib/cassandra_object/persistence.rb +195 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/type_registration.rb +7 -0
- data/lib/cassandra_object/types.rb +128 -0
- data/lib/cassandra_object/validation.rb +49 -0
- data/test/basic_scenarios_test.rb +243 -0
- data/test/callbacks_test.rb +19 -0
- data/test/config/cassandra.in.sh +53 -0
- data/test/config/log4j.properties +38 -0
- data/test/config/storage-conf.xml +221 -0
- data/test/connection.rb +25 -0
- data/test/cursor_test.rb +66 -0
- data/test/dirty_test.rb +34 -0
- data/test/fixture_models.rb +90 -0
- data/test/identity/natural_key_factory_test.rb +94 -0
- data/test/index_test.rb +69 -0
- data/test/legacy/test_helper.rb +18 -0
- data/test/migration_test.rb +21 -0
- data/test/one_to_many_associations_test.rb +163 -0
- data/test/test_case.rb +28 -0
- data/test/test_helper.rb +16 -0
- data/test/time_test.rb +32 -0
- data/test/types_test.rb +252 -0
- data/test/validation_test.rb +25 -0
- data/test/z_mock_test.rb +36 -0
- metadata +243 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Identity
|
4
|
+
class NaturalKeyFactoryTest < CassandraObjectTestCase
|
5
|
+
context "With one attribute" do
|
6
|
+
setup do
|
7
|
+
@key_factory = CassandraObject::Identity::NaturalKeyFactory.new :attributes => :name
|
8
|
+
@james = Person.new("name" => "james")
|
9
|
+
end
|
10
|
+
|
11
|
+
should "have a key whose string and param value is the value of that attribute" do
|
12
|
+
@key = @key_factory.next_key(@james)
|
13
|
+
|
14
|
+
assert_equal "james", @key.to_s
|
15
|
+
assert_equal "james", @key.to_param
|
16
|
+
end
|
17
|
+
|
18
|
+
should "parse to a key from a param value" do
|
19
|
+
@key = @key_factory.parse("james")
|
20
|
+
|
21
|
+
assert_equal "james", @key.to_s
|
22
|
+
assert_equal "james", @key.to_param
|
23
|
+
end
|
24
|
+
|
25
|
+
should "create a key from a value" do
|
26
|
+
@key = @key_factory.create("james")
|
27
|
+
|
28
|
+
assert_equal "james", @key.to_s
|
29
|
+
assert_equal "james", @key.to_param
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "With multiple attributes" do
|
34
|
+
setup do
|
35
|
+
@key_factory = CassandraObject::Identity::NaturalKeyFactory.new :attributes => [:name, :age]
|
36
|
+
@james = Person.new("name" => "james", "age" => 23)
|
37
|
+
end
|
38
|
+
|
39
|
+
should "create a key whose string value is the two values, joined with a separator" do
|
40
|
+
key = @key_factory.next_key(@james)
|
41
|
+
|
42
|
+
assert_equal "james-23", key.to_s
|
43
|
+
assert_equal "james-23", key.to_param
|
44
|
+
end
|
45
|
+
|
46
|
+
should "parse the key" do
|
47
|
+
key = @key_factory.parse("james-23")
|
48
|
+
|
49
|
+
assert_equal "james-23", key.to_s
|
50
|
+
assert_equal "james-23", key.to_param
|
51
|
+
end
|
52
|
+
|
53
|
+
should "create the key" do
|
54
|
+
key = @key_factory.create("james-23")
|
55
|
+
|
56
|
+
assert_equal "james-23", key.to_s
|
57
|
+
assert_equal "james-23", key.to_param
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "With a custom separator" do
|
62
|
+
setup do
|
63
|
+
@key_factory = CassandraObject::Identity::NaturalKeyFactory.new :attributes => [:name, :age],
|
64
|
+
:separator => "#"
|
65
|
+
@james = Person.new("name" => "james", "age" => 23)
|
66
|
+
end
|
67
|
+
|
68
|
+
should "join the attributes with the custom separator" do
|
69
|
+
key = @key_factory.next_key(@james)
|
70
|
+
|
71
|
+
assert_equal "james#23", key.to_s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "Natural keys" do
|
76
|
+
setup do
|
77
|
+
@james = CassandraObject::Identity::NaturalKeyFactory::NaturalKey.new("james")
|
78
|
+
@another_james = CassandraObject::Identity::NaturalKeyFactory::NaturalKey.new("james")
|
79
|
+
@joe = CassandraObject::Identity::NaturalKeyFactory::NaturalKey.new("joe")
|
80
|
+
end
|
81
|
+
|
82
|
+
should "be equal, if their values are equal" do
|
83
|
+
assert @james == @another_james
|
84
|
+
assert @james.eql?(@another_james)
|
85
|
+
end
|
86
|
+
|
87
|
+
should "not be equal if their values are different" do
|
88
|
+
assert @james != @joe
|
89
|
+
assert !@james.eql?(@joe)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
data/test/index_test.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class IndexTest < CassandraObjectTestCase
|
4
|
+
context "A non-unique index" do
|
5
|
+
setup do
|
6
|
+
@last_name = ActiveSupport::SecureRandom.hex(5)
|
7
|
+
@koz = Customer.create :first_name=>"Michael", :last_name=>@last_name, :date_of_birth=>28.years.ago.to_date
|
8
|
+
@wife = Customer.create :first_name=>"Anika", :last_name=>@last_name, :date_of_birth=>30.years.ago.to_date
|
9
|
+
end
|
10
|
+
|
11
|
+
should "Return both values" do
|
12
|
+
assert_ordered [@wife.key, @koz.key], Customer.find_all_by_last_name(@last_name).map(&:key)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "return the older when the newer is destroyed" do
|
16
|
+
@wife.destroy
|
17
|
+
assert_equal [@koz.key], Customer.find_all_by_last_name(@last_name).map(&:key)
|
18
|
+
end
|
19
|
+
|
20
|
+
should "return a single value when the original one is changed" do
|
21
|
+
@wife.last_name = "WTF"
|
22
|
+
@wife.save
|
23
|
+
assert_equal [@koz.key], Customer.find_all_by_last_name(@last_name).map(&:key)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "A corrupt non-unique index" do
|
28
|
+
setup do
|
29
|
+
@last_name = ActiveSupport::SecureRandom.hex(5)
|
30
|
+
@koz = Customer.create :first_name=>"Michael", :last_name=>@last_name, :date_of_birth=>28.years.ago.to_date
|
31
|
+
connection.insert("CustomersByLastName", @last_name, {"last_name"=>{SimpleUUID::UUID.new=>"ROFLSKATES"}})
|
32
|
+
@wife = Customer.create :first_name=>"Anika", :last_name=>@last_name, :date_of_birth=>30.years.ago.to_date
|
33
|
+
end
|
34
|
+
|
35
|
+
should "Return both values and clean up" do
|
36
|
+
assert_ordered [@wife.key, @koz.key], Customer.find_all_by_last_name(@last_name).map(&:key)
|
37
|
+
assert_ordered [@wife.key, @koz.key], connection.get("CustomersByLastName", @last_name, "last_name", :reversed=>true).values
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
context "A unique index" do
|
43
|
+
setup do
|
44
|
+
@invoice = mock_invoice
|
45
|
+
@number = @invoice.number
|
46
|
+
end
|
47
|
+
|
48
|
+
should "return the right record" do
|
49
|
+
assert_equal @invoice, Invoice.find_by_number(@number)
|
50
|
+
end
|
51
|
+
|
52
|
+
should "return nil after destroy" do
|
53
|
+
@invoice.destroy
|
54
|
+
assert_nil Invoice.find_by_number(@number)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context " A corrupt unique index" do
|
59
|
+
setup do
|
60
|
+
connection.insert("InvoicesByNumber", '15' , {"key"=>"HAHAHAHA"})
|
61
|
+
end
|
62
|
+
|
63
|
+
should "return nil on fetch and cleanup" do
|
64
|
+
assert_nil Invoice.find_by_number(15)
|
65
|
+
assert connection.get("InvoicesByNumber", "15", "number").blank?
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
gem 'activesupport', "~> 2.3.5"
|
4
|
+
|
5
|
+
puts "LEGACY TEST"
|
6
|
+
|
7
|
+
require 'cassandra_object'
|
8
|
+
require 'connection'
|
9
|
+
|
10
|
+
require 'test/unit'
|
11
|
+
require 'active_support/test_case'
|
12
|
+
require 'shoulda'
|
13
|
+
|
14
|
+
require 'fixture_models'
|
15
|
+
require 'pp'
|
16
|
+
|
17
|
+
require 'test_case'
|
18
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MigrationTest < CassandraObjectTestCase
|
4
|
+
test "a new invoice should have the right schema version" do
|
5
|
+
i = mock_invoice
|
6
|
+
assert_equal 2, i.schema_version
|
7
|
+
end
|
8
|
+
|
9
|
+
test " a new invoice should have an empty gst_number" do
|
10
|
+
assert_equal nil, mock_invoice.gst_number
|
11
|
+
end
|
12
|
+
|
13
|
+
test "an old invoice should get fetched and updated" do
|
14
|
+
key = Invoice.next_key.to_s
|
15
|
+
connection.insert(Invoice.column_family, key, {"schema_version"=>"1", "number"=>"200", "total"=>"150.35"})
|
16
|
+
|
17
|
+
invoice = Invoice.get(key)
|
18
|
+
assert_equal 2, invoice.schema_version
|
19
|
+
assert_equal 150.35, invoice.total
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class OneToManyAssociationsTest < CassandraObjectTestCase
|
4
|
+
|
5
|
+
context "A customer with no invoices added to its invoice association" 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
|
+
assert @customer.valid?, @customer.errors
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return an empty array for the association" do
|
15
|
+
assert_equal [], @customer.invoices.to_a
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "A customer with an invoice added to its invoice association" do
|
20
|
+
setup do
|
21
|
+
@customer = Customer.create :first_name => "Michael",
|
22
|
+
:last_name => "Koziarski",
|
23
|
+
:date_of_birth => Date.parse("1980/08/15")
|
24
|
+
|
25
|
+
assert @customer.valid?, @customer.errors
|
26
|
+
|
27
|
+
@invoice = mock_invoice
|
28
|
+
assert @invoice.valid?, @invoice.errors
|
29
|
+
|
30
|
+
@customer.invoices << @invoice
|
31
|
+
end
|
32
|
+
|
33
|
+
should "have set the inverse" do
|
34
|
+
assert_equal @customer, @invoice.customer
|
35
|
+
end
|
36
|
+
|
37
|
+
should "have also written to cassandra" do
|
38
|
+
assert_equal @invoice, @customer.invoices.to_a.first
|
39
|
+
end
|
40
|
+
|
41
|
+
context "Simple Read-Repair" do
|
42
|
+
setup do
|
43
|
+
add_junk_key
|
44
|
+
assert_ordered ["SomethingStupid", @invoice.key], association_keys_in_cassandra
|
45
|
+
end
|
46
|
+
|
47
|
+
should "tidy up when fetching" do
|
48
|
+
assert_equal [@invoice], @customer.invoices.all
|
49
|
+
assert_equal [@invoice.key.to_s], association_keys_in_cassandra
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "More complicated Read-Repair" do
|
54
|
+
setup do
|
55
|
+
# Now add a second legit invoice
|
56
|
+
@second_invoice = mock_invoice
|
57
|
+
@customer.invoices << @second_invoice
|
58
|
+
|
59
|
+
add_junk_key
|
60
|
+
|
61
|
+
@third_invoice = mock_invoice
|
62
|
+
@customer.invoices << @third_invoice
|
63
|
+
|
64
|
+
#
|
65
|
+
|
66
|
+
assert_ordered [@third_invoice.key,"SomethingStupid", @second_invoice.key, @invoice.key],
|
67
|
+
association_keys_in_cassandra
|
68
|
+
end
|
69
|
+
|
70
|
+
should "return the last one when passed a limit of one, and not touch the keys" do
|
71
|
+
assert_equal [@third_invoice], @customer.invoices.all(:limit=>1)
|
72
|
+
assert_ordered [@third_invoice.key,"SomethingStupid", @second_invoice.key, @invoice.key],
|
73
|
+
association_keys_in_cassandra
|
74
|
+
end
|
75
|
+
|
76
|
+
should "return them all when passed a limit of 3, and clean up the keys" do
|
77
|
+
assert_ordered [@third_invoice, @second_invoice, @invoice], @customer.invoices.all(:limit=>3), false
|
78
|
+
assert_ordered [@third_invoice.key, @second_invoice.key, @invoice.key],
|
79
|
+
association_keys_in_cassandra
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
should "return the first invoice when told to start after the second" do
|
84
|
+
assert_ordered [@invoice.key], @customer.invoices.all(:limit=>1, :start_after=>index_key_for(@second_invoice)).map(&:key)
|
85
|
+
assert_ordered [@third_invoice.key,"SomethingStupid", @second_invoice.key, @invoice.key],
|
86
|
+
association_keys_in_cassandra
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "A customer with an invoice added to its paid invoices association (which has no explicit reversed option)" do
|
92
|
+
setup do
|
93
|
+
@customer = Customer.create :first_name => "Michael",
|
94
|
+
:last_name => "Koziarski",
|
95
|
+
:date_of_birth => Date.parse("1980/08/15")
|
96
|
+
|
97
|
+
assert @customer.valid?, @customer.errors
|
98
|
+
|
99
|
+
@invoice = mock_invoice
|
100
|
+
assert @invoice.valid?, @invoice.errors
|
101
|
+
|
102
|
+
@customer.paid_invoices << @invoice
|
103
|
+
end
|
104
|
+
|
105
|
+
should "return invoice from association" do
|
106
|
+
assert_equal @invoice, @customer.paid_invoices.to_a.first
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "Association proxy create" do
|
111
|
+
setup do
|
112
|
+
@customer = Customer.create! :first_name => "Michael",
|
113
|
+
:last_name => "Koziarski",
|
114
|
+
:date_of_birth => Date.parse("1980/08/15")
|
115
|
+
@invoice = @customer.invoices.create :number=>50, :total=>25.0
|
116
|
+
end
|
117
|
+
|
118
|
+
should "return the invoice" do
|
119
|
+
assert_kind_of Invoice, @invoice
|
120
|
+
end
|
121
|
+
|
122
|
+
should "have set the attributes" do
|
123
|
+
assert_equal 50, @invoice.number
|
124
|
+
assert_equal 25.0, @invoice.total
|
125
|
+
end
|
126
|
+
|
127
|
+
should "have set the inverse" do
|
128
|
+
assert_equal @customer, @invoice.customer
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "Association proxy all" do
|
133
|
+
setup do
|
134
|
+
@customer = Customer.create! :first_name => "Michael",
|
135
|
+
:last_name => "Koziarski",
|
136
|
+
:date_of_birth => Date.parse("1980/08/15")
|
137
|
+
@first = @customer.invoices.create :number => 50, :total => 25.0
|
138
|
+
@second = @customer.invoices.create :number => 50, :total => 25.0
|
139
|
+
end
|
140
|
+
|
141
|
+
should "suport overriding :reversed value" do
|
142
|
+
assert_ordered [@first.key, @second.key], @customer.invoices.all(:reversed => false).map(&:key)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_junk_key
|
147
|
+
Customer.associations[:invoices].add(@customer, MockRecord.new("SomethingStupid"))
|
148
|
+
end
|
149
|
+
|
150
|
+
def association_keys_in_cassandra
|
151
|
+
Customer.connection.get(Customer.associations[:invoices].column_family, @customer.key.to_s, "invoices", :reversed=>true).values
|
152
|
+
end
|
153
|
+
|
154
|
+
def index_key_for(object)
|
155
|
+
Customer.connection.get(Customer.associations[:invoices].column_family, @customer.key.to_s, "invoices").each do |(key, value)|
|
156
|
+
if value == object.key.to_s
|
157
|
+
return key
|
158
|
+
end
|
159
|
+
end
|
160
|
+
raise "Not found"
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
data/test/test_case.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
class CassandraObjectTestCase < ActiveSupport::TestCase
|
3
|
+
def teardown
|
4
|
+
CassandraObject::Base.connection.clear_keyspace!
|
5
|
+
end
|
6
|
+
|
7
|
+
def mock_invoice
|
8
|
+
Invoice.create :number=>Time.now.to_i*(rand(5)), :total=>Time.now.to_f
|
9
|
+
end
|
10
|
+
|
11
|
+
def connection
|
12
|
+
CassandraObject::Base.connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert_ordered(expected_object_order, actual_order, to_s_before_comparing = true)
|
16
|
+
# don't use map! so we don't go changing user's arguments
|
17
|
+
if to_s_before_comparing
|
18
|
+
expected_object_order = expected_object_order.map(&:to_s)
|
19
|
+
actual_order = actual_order.map(&:to_s)
|
20
|
+
end
|
21
|
+
|
22
|
+
assert_equal Set.new(expected_object_order), Set.new(actual_order), "Collections weren't equal"
|
23
|
+
actual_indexes = actual_order.map do |e|
|
24
|
+
expected_object_order.index(e)
|
25
|
+
end
|
26
|
+
assert_equal expected_object_order, actual_order, "Collection was ordered incorrectly: #{actual_indexes.inspect}"
|
27
|
+
end
|
28
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
|
4
|
+
Bundler.setup
|
5
|
+
|
6
|
+
require 'cassandra_object'
|
7
|
+
require 'connection'
|
8
|
+
|
9
|
+
require 'test/unit'
|
10
|
+
require 'active_support/test_case'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
require 'fixture_models'
|
14
|
+
require 'pp'
|
15
|
+
|
16
|
+
require 'test_case'
|
data/test/time_test.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TimeTest < CassandraObjectTestCase
|
4
|
+
|
5
|
+
test "new raises an error" do
|
6
|
+
begin
|
7
|
+
appt = Appointment.new :start_time => 1
|
8
|
+
flunk "Should have failed to save"
|
9
|
+
rescue ArgumentError => e
|
10
|
+
assert_equal "CassandraObject::TimeType requires a Time", e.message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
test "the attribute writer raises an error" do
|
15
|
+
begin
|
16
|
+
appt = Appointment.new
|
17
|
+
appt.start_time = 1
|
18
|
+
flunk "Should have failed to save"
|
19
|
+
rescue ArgumentError => e
|
20
|
+
assert_equal "CassandraObject::TimeType requires a Time", e.message
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
test "Time's should be round-trip-able" do
|
25
|
+
appt = Appointment.new :start_time => (t = Time.now.utc), :title => "team meeting"
|
26
|
+
appt.save!
|
27
|
+
appt2 = Appointment.get(appt.key)
|
28
|
+
|
29
|
+
assert_equal appt.start_time.class, appt2.start_time.class
|
30
|
+
assert_equal appt.start_time, appt2.start_time
|
31
|
+
end
|
32
|
+
end
|