gotime-cassandra_object 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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,6 @@
1
+ module CassandraObject
2
+ module Serialization
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Serializers::JSON
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ CassandraObject::Base.register_attribute_type(:integer, Integer, CassandraObject::IntegerType)
2
+ CassandraObject::Base.register_attribute_type(:float, Float, CassandraObject::FloatType)
3
+ CassandraObject::Base.register_attribute_type(:date, Date, CassandraObject::DateType)
4
+ CassandraObject::Base.register_attribute_type(:time, Time, CassandraObject::TimeType)
5
+ CassandraObject::Base.register_attribute_type(:time_with_zone, ActiveSupport::TimeWithZone, CassandraObject::TimeWithZoneType)
6
+ CassandraObject::Base.register_attribute_type(:string, String, CassandraObject::StringType)
7
+ CassandraObject::Base.register_attribute_type(:hash, Hash, CassandraObject::HashType)
@@ -0,0 +1,128 @@
1
+ module CassandraObject
2
+ module IntegerType
3
+ REGEX = /\A[-+]?\d+\Z/
4
+ def encode(int)
5
+ return '' if int.nil?
6
+ raise ArgumentError.new("#{self} requires an Integer. You passed #{int.inspect}") unless int.kind_of?(Integer)
7
+ int.to_s
8
+ end
9
+ module_function :encode
10
+
11
+ def decode(str)
12
+ return nil if str.empty?
13
+ raise ArgumentError.new("#{str} isn't a String that looks like a Integer") unless str.kind_of?(String) && str.match(REGEX)
14
+ str.to_i
15
+ end
16
+ module_function :decode
17
+ end
18
+
19
+ module FloatType
20
+ REGEX = /\A[-+]?\d+(\.\d+)\Z/
21
+ def encode(float)
22
+ return '' if float.nil?
23
+ raise ArgumentError.new("#{self} requires a Float") unless float.kind_of?(Float)
24
+ float.to_s
25
+ end
26
+ module_function :encode
27
+
28
+ def decode(str)
29
+ return nil if str == ''
30
+ raise ArgumentError.new("#{str} isn't a String that looks like a Float") unless str.kind_of?(String) && str.match(REGEX)
31
+ str.to_f
32
+ end
33
+ module_function :decode
34
+ end
35
+
36
+ module DateType
37
+ FORMAT = '%Y-%m-%d'
38
+ REGEX = /\A\d{4}-\d{2}-\d{2}\Z/
39
+ def encode(date)
40
+ raise ArgumentError.new("#{self} requires a Date") unless date.kind_of?(Date)
41
+ date.strftime(FORMAT)
42
+ end
43
+ module_function :encode
44
+
45
+ def decode(str)
46
+ raise ArgumentError.new("#{str} isn't a String that looks like a Date") unless str.kind_of?(String) && str.match(REGEX)
47
+ Date.strptime(str, FORMAT)
48
+ end
49
+ module_function :decode
50
+ end
51
+
52
+ module TimeType
53
+ # lifted from the implementation of Time.xmlschema and simplified
54
+ REGEX = /\A\s*
55
+ (-?\d+)-(\d\d)-(\d\d)
56
+ T
57
+ (\d\d):(\d\d):(\d\d)
58
+ (\.\d*)?
59
+ (Z|[+-]\d\d:\d\d)?
60
+ \s*\z/ix
61
+
62
+ def encode(time)
63
+ raise ArgumentError.new("#{self} requires a Time") unless time.kind_of?(Time)
64
+ time.xmlschema(6)
65
+ end
66
+ module_function :encode
67
+
68
+ def decode(str)
69
+ raise ArgumentError.new("#{str} isn't a String that looks like a Time") unless str.kind_of?(String) && str.match(REGEX)
70
+ Time.xmlschema(str)
71
+ end
72
+ module_function :decode
73
+ end
74
+
75
+ module TimeWithZoneType
76
+ def encode(time)
77
+ TimeType.encode(time.utc)
78
+ end
79
+ module_function :encode
80
+
81
+ def decode(str)
82
+ TimeType.decode(str).in_time_zone
83
+ end
84
+ module_function :decode
85
+ end
86
+
87
+ module StringType
88
+ def encode(str)
89
+ raise ArgumentError.new("#{self} requires a String") unless str.kind_of?(String)
90
+ str
91
+ end
92
+ module_function :encode
93
+
94
+ def decode(str)
95
+ str
96
+ end
97
+ module_function :decode
98
+ end
99
+
100
+ module HashType
101
+ def encode(hash)
102
+ raise ArgumentError.new("#{self} requires a Hash") unless hash.kind_of?(Hash)
103
+ ActiveSupport::JSON.encode(hash)
104
+ end
105
+ module_function :encode
106
+
107
+ def decode(str)
108
+ ActiveSupport::JSON.decode(str)
109
+ end
110
+ module_function :decode
111
+ end
112
+
113
+ module BooleanType
114
+ ALLOWED = [true, false, nil]
115
+ def encode(bool)
116
+ unless ALLOWED.any?{ |a| bool == a }
117
+ raise ArgumentError.new("#{self} requires a Boolean or nil")
118
+ end
119
+ bool ? '1' : '0'
120
+ end
121
+ module_function :encode
122
+
123
+ def decode(bool)
124
+ bool == '1'
125
+ end
126
+ module_function :decode
127
+ end
128
+ end
@@ -0,0 +1,49 @@
1
+ module CassandraObject
2
+ module Validation
3
+ class RecordInvalidError < StandardError
4
+ attr_reader :record
5
+ def initialize(record)
6
+ @record = record
7
+ super("Invalid record: #{@record.errors.full_messages.to_sentence}")
8
+ end
9
+
10
+ def self.raise_error(record)
11
+ raise new(record)
12
+ end
13
+ end
14
+ extend ActiveSupport::Concern
15
+ include ActiveModel::Validations
16
+
17
+ included do
18
+ define_model_callbacks :validation
19
+ define_callbacks :validate, :scope => :name
20
+ end
21
+
22
+ module ClassMethods
23
+ def create!(attributes)
24
+ new(attributes).tap &:save!
25
+ end
26
+ end
27
+
28
+ module InstanceMethods
29
+ def valid?
30
+ run_callbacks :validation do
31
+ super
32
+ end
33
+ end
34
+
35
+ def save
36
+ if valid?
37
+ super
38
+ else
39
+ false
40
+ end
41
+ end
42
+
43
+ def save!
44
+ save || RecordInvalidError.raise_error(self)
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,243 @@
1
+ require 'test_helper'
2
+
3
+ class BasicScenariosTest < CassandraObjectTestCase
4
+ def setup
5
+ super
6
+ @customer = Customer.create :first_name => "Michael",
7
+ :last_name => "Koziarski",
8
+ :date_of_birth => Date.parse("1980/08/15")
9
+ @customer_key = @customer.key.to_s
10
+
11
+ assert @customer.valid?
12
+ end
13
+
14
+ test "get on a non-existent key returns nil" do
15
+ assert_nil Customer.get("THIS IS NOT A KEY")
16
+ end
17
+
18
+ test "a new object can be retrieved by key" do
19
+ other_customer = Customer.get(@customer_key)
20
+ assert_equal @customer, other_customer
21
+
22
+ assert_equal "Michael", other_customer.first_name
23
+ assert_equal "Koziarski", other_customer.last_name
24
+ assert_equal Date.parse("1980-08-15"), other_customer.date_of_birth
25
+ end
26
+
27
+ test "a new object is included in Model.all" do
28
+ assert Customer.all.include?(@customer)
29
+ end
30
+
31
+ test "date_of_birth is a date" do
32
+ assert @customer.date_of_birth.is_a?(Date)
33
+ end
34
+
35
+ test "should not let you assign junk to a date column" do
36
+ assert_raise(ArgumentError) do
37
+ @customer.date_of_birth = 24.5
38
+ end
39
+ end
40
+
41
+ test "should return nil for attributes without a value" do
42
+ assert_nil @customer.preferences
43
+ end
44
+
45
+ test "should let a user set a Hash valued attribute" do
46
+ val = {"a"=>"b"}
47
+ @customer.preferences = val
48
+ assert_equal val, @customer.preferences
49
+ @customer.save
50
+
51
+ other_customer = Customer.get(@customer_key)
52
+ assert_equal val, other_customer.preferences
53
+ end
54
+
55
+ test "should validate strings passed to a typed column" do
56
+ assert_raises(ArgumentError){
57
+ @customer.date_of_birth = "35345908"
58
+ }
59
+ end
60
+
61
+ test "should have a schema version of 0" do
62
+ assert_equal 0, @customer.schema_version
63
+ end
64
+
65
+ test "multiget" do
66
+ custs = Customer.multi_get([@customer_key, "This is not a key either"])
67
+ customer, nothing = custs.values
68
+
69
+ assert_equal @customer, customer
70
+ assert_nil nothing
71
+ end
72
+
73
+ test "creating a new record starts with the right version" do
74
+ @invoice = mock_invoice
75
+
76
+ raw_result = Invoice.connection.get("Invoices", @invoice.key.to_s)
77
+ assert_equal Invoice.current_schema_version, ActiveSupport::JSON.decode(raw_result["schema_version"])
78
+ end
79
+
80
+ test "to_param works" do
81
+ invoice = mock_invoice
82
+ param = invoice.to_param
83
+ assert_match /[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}/, param
84
+ assert_equal invoice.key, Invoice.parse_key(param)
85
+ end
86
+
87
+ test "setting a column_family" do
88
+ class Foo < CassandraObject::Base
89
+ self.column_family = 'Bar'
90
+ end
91
+ assert_equal 'Bar', Foo.column_family
92
+ end
93
+
94
+ context "destroying a customer with invoices" do
95
+ setup do
96
+ @invoice = mock_invoice
97
+ @customer.invoices << @invoice
98
+
99
+ @customer.destroy
100
+ end
101
+
102
+ should "Have removed the customer" do
103
+ assert Customer.connection.get("Customers", @customer.key.to_s).empty?
104
+ end
105
+
106
+ should "Have removed the associations too" do
107
+ assert_equal Hash.new, Customer.connection.get("CustomerRelationships", @customer.key.to_s)
108
+ end
109
+ end
110
+
111
+ context "An object with a natural key" do
112
+ setup do
113
+ @payment = Payment.new :reference_number => "12345",
114
+ :amount => 1001
115
+ @payment.save!
116
+ end
117
+
118
+ should "create a natural key based on that attr" do
119
+ assert_equal "12345", @payment.key.to_s
120
+ end
121
+
122
+ should "have a key equal to another object with that key" do
123
+ p = Payment.new(:reference_number => "12345",
124
+ :amount => 1001)
125
+ p.save
126
+
127
+ assert_equal @payment.key, p.key
128
+ end
129
+ end
130
+
131
+ context "Model with no attributes" do
132
+ setup do
133
+ class Empty < CassandraObject::Base
134
+ end
135
+ end
136
+
137
+ should "work" do
138
+ e = Empty.new
139
+ end
140
+ end
141
+
142
+ context "A model that allows nils" do
143
+ setup do
144
+ class Nilable < CassandraObject::Base
145
+ attribute :user_id, :type => Integer, :allow_nil => true
146
+ end
147
+ end
148
+
149
+ should "should be valid with a nil" do
150
+ n = Nilable.new
151
+ assert n.valid?
152
+ end
153
+ end
154
+
155
+ context "A janky custom key factory" do
156
+ setup do
157
+ class JankyKeys
158
+ def next_key(object)
159
+ nil
160
+ end
161
+ end
162
+ class JankyObject < CassandraObject::Base
163
+ key JankyKeys.new
164
+ end
165
+ @object = JankyObject.new
166
+ end
167
+
168
+ should "raise an error on nil key" do
169
+ assert_raises(RuntimeError) do
170
+ @object.save
171
+ end
172
+ end
173
+ end
174
+
175
+ test "updating columns" do
176
+ appt = Appointment.new(:start_time => Time.now, :title => 'emergency meeting')
177
+ appt.save!
178
+ appt = Appointment.get(appt.key)
179
+ appt.start_time = Time.now + 1.hour
180
+ appt.end_time = Time.now.utc + 5.hours
181
+ appt.save!
182
+ assert appt.reload.end_time.is_a?(ActiveSupport::TimeWithZone)
183
+ end
184
+
185
+ test "Saving a class with custom attributes uses the custom converter" do
186
+ @customer.custom_storage = "hello"
187
+ @customer.save
188
+
189
+ raw_result = Customer.connection.get("Customers", @customer.key.to_s)
190
+
191
+ assert_equal "olleh", raw_result["custom_storage"]
192
+ assert_equal "hello", @customer.reload.custom_storage
193
+
194
+ end
195
+
196
+ context "setting valid consistency levels" do
197
+ setup do
198
+ class Senate < CassandraObject::Base
199
+ consistency_levels :write => :quorum, :read => :quorum
200
+ end
201
+ end
202
+
203
+ should "should have the settings" do
204
+ assert_equal :quorum, Senate.write_consistency
205
+ assert_equal :quorum, Senate.read_consistency
206
+ end
207
+ end
208
+
209
+ context "setting invalid consistency levels" do
210
+ context "invalid write consistency" do
211
+ should "raise an error" do
212
+ assert_raises(ArgumentError) do
213
+ class BadWriter < CassandraObject::Base
214
+ consistency_levels :write => :foo, :read => :quorum
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ context "invalid read consistency" do
221
+ should "raise an error" do
222
+ assert_raises(ArgumentError) do
223
+ class BadReader < CassandraObject::Base
224
+ consistency_levels :write => :quorum, :read => :foo
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+
231
+ test "ignoring columns we don't know about" do
232
+ # if there's a column in the row that's not configured as an attribute, it should be ignored with no errors
233
+
234
+ payment = Payment.new(:reference_number => 'abc123', :amount => 26)
235
+ payment.save
236
+
237
+ Payment.connection.insert(Payment.column_family, payment.key.to_s, {"bogus" => 'very bogus', "schema_version" => payment.schema_version.to_s}, :consistency => Payment.send(:write_consistency_for_thrift))
238
+
239
+ assert_nothing_raised do
240
+ Payment.get(payment.key)
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class CallbacksTest < CassandraObjectTestCase
4
+ def setup
5
+ super
6
+ end
7
+
8
+ context "a newly created record" do
9
+ setup do
10
+ @customer = Customer.create! :first_name => "Tom",
11
+ :last_name => "Ward",
12
+ :date_of_birth => Date.parse("1977/12/04")
13
+ end
14
+
15
+ should "have had after_create called" do
16
+ assert @customer.after_create_called?
17
+ end
18
+ end
19
+ end