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,77 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class OneToOneAssociation
|
3
|
+
def initialize(association_name, owner_class, options)
|
4
|
+
@association_name = association_name.to_s
|
5
|
+
@owner_class = owner_class
|
6
|
+
@target_class_name = options[:class_name] || association_name.to_s.camelize
|
7
|
+
@options = options
|
8
|
+
|
9
|
+
define_methods!
|
10
|
+
end
|
11
|
+
|
12
|
+
def define_methods!
|
13
|
+
@owner_class.class_eval <<-eos
|
14
|
+
def #{@association_name}
|
15
|
+
@_#{@association_name} ||= self.class.associations[:#{@association_name}].find(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def #{@association_name}=(record)
|
19
|
+
@_#{@association_name} = record
|
20
|
+
self.class.associations[:#{@association_name}].set(self, record)
|
21
|
+
end
|
22
|
+
eos
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear(owner)
|
26
|
+
connection.remove(column_family, owner.key.to_s, @association_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def find(owner)
|
30
|
+
if key = connection.get(column_family, owner.key.to_s, @association_name.to_s, :count=>1).values.first
|
31
|
+
target_class.get(key)
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def set(owner, record, set_inverse = true)
|
38
|
+
clear(owner)
|
39
|
+
connection.insert(column_family, owner.key.to_s, {@association_name=>{new_key => record.key.to_s}})
|
40
|
+
if has_inverse? && set_inverse
|
41
|
+
inverse.set_inverse(record, owner)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def new_key
|
46
|
+
SimpleUUID::UUID.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_inverse(owner, record)
|
50
|
+
set(owner, record, false)
|
51
|
+
end
|
52
|
+
|
53
|
+
def has_inverse?
|
54
|
+
@options[:inverse_of]
|
55
|
+
end
|
56
|
+
|
57
|
+
def inverse
|
58
|
+
has_inverse? && target_class.associations[@options[:inverse_of]]
|
59
|
+
end
|
60
|
+
|
61
|
+
def column_family
|
62
|
+
@owner_class.to_s + "Relationships"
|
63
|
+
end
|
64
|
+
|
65
|
+
def connection
|
66
|
+
@owner_class.connection
|
67
|
+
end
|
68
|
+
|
69
|
+
def target_class
|
70
|
+
@target_class ||= @target_class_name.constantize
|
71
|
+
end
|
72
|
+
|
73
|
+
def new_proxy(owner)
|
74
|
+
# OneToManyAssociationProxy.new(self, owner)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
attr_reader :name, :converter, :expected_type
|
5
|
+
def initialize(name, owner_class, converter, expected_type, options)
|
6
|
+
@name = name.to_s
|
7
|
+
@owner_class = owner_class
|
8
|
+
@converter = converter
|
9
|
+
@expected_type = expected_type
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_value!(value)
|
14
|
+
converter.encode(value) unless value.nil? && @options[:allow_nil]
|
15
|
+
value
|
16
|
+
end
|
17
|
+
|
18
|
+
def define_methods!
|
19
|
+
@owner_class.define_attribute_methods(true)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Attributes
|
24
|
+
extend ActiveSupport::Concern
|
25
|
+
include ActiveModel::AttributeMethods
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def attribute(name, options)
|
29
|
+
|
30
|
+
unless type_mapping = attribute_types[options[:type]]
|
31
|
+
type_mapping = { :expected_type => options[:type],
|
32
|
+
:converter => options[:converter] }.with_indifferent_access
|
33
|
+
end
|
34
|
+
|
35
|
+
new_attr = Attribute.new(name, self, type_mapping[:converter], type_mapping[:expected_type], options)
|
36
|
+
write_inheritable_hash(:model_attributes, {name => new_attr}.with_indifferent_access)
|
37
|
+
new_attr.define_methods!
|
38
|
+
end
|
39
|
+
|
40
|
+
def define_attribute_methods(force = false)
|
41
|
+
return unless model_attributes
|
42
|
+
undefine_attribute_methods if force
|
43
|
+
super(model_attributes.keys)
|
44
|
+
end
|
45
|
+
|
46
|
+
def register_attribute_type(name, expected_type, converter)
|
47
|
+
attribute_types[name] = { :expected_type => expected_type, :converter => converter }.with_indifferent_access
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
included do
|
52
|
+
class_inheritable_hash :model_attributes
|
53
|
+
attribute_method_suffix("", "=")
|
54
|
+
|
55
|
+
cattr_accessor :attribute_types
|
56
|
+
self.attribute_types = {}.with_indifferent_access
|
57
|
+
end
|
58
|
+
|
59
|
+
module InstanceMethods
|
60
|
+
def write_attribute(name, value)
|
61
|
+
if ma = self.class.model_attributes[name]
|
62
|
+
@attributes[name.to_s] = ma.check_value!(value)
|
63
|
+
else
|
64
|
+
raise NoMethodError, "Unknown attribute #{name.inspect}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def read_attribute(name)
|
69
|
+
@attributes[name.to_s]
|
70
|
+
end
|
71
|
+
|
72
|
+
def attributes=(attributes)
|
73
|
+
attributes.each do |(name, value)|
|
74
|
+
send("#{name}=", value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
def attribute_method?(name)
|
80
|
+
!!model_attributes[name.to_sym]
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def attribute(name)
|
85
|
+
read_attribute(name.to_sym)
|
86
|
+
end
|
87
|
+
|
88
|
+
def attribute=(name, value)
|
89
|
+
write_attribute(name.to_sym, value)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'cassandra'
|
2
|
+
require 'set'
|
3
|
+
require 'cassandra_object/attributes'
|
4
|
+
require 'cassandra_object/dirty'
|
5
|
+
require 'cassandra_object/persistence'
|
6
|
+
|
7
|
+
require 'cassandra_object/callbacks'
|
8
|
+
|
9
|
+
require 'cassandra_object/validation'
|
10
|
+
require 'cassandra_object/identity'
|
11
|
+
require 'cassandra_object/indexes'
|
12
|
+
require 'cassandra_object/serialization'
|
13
|
+
require 'cassandra_object/associations'
|
14
|
+
#require 'cassandra_object/sets'
|
15
|
+
require 'cassandra_object/migrations'
|
16
|
+
require 'cassandra_object/cursor'
|
17
|
+
require 'cassandra_object/collection'
|
18
|
+
require 'cassandra_object/types'
|
19
|
+
require 'cassandra_object/mocking'
|
20
|
+
|
21
|
+
require 'cassandra_object/log_subscriber'
|
22
|
+
|
23
|
+
module CassandraObject
|
24
|
+
class Base
|
25
|
+
class_inheritable_accessor :connection
|
26
|
+
class_inheritable_writer :connection_class
|
27
|
+
|
28
|
+
def self.connection_class
|
29
|
+
read_inheritable_attribute(:connection_class) || Cassandra
|
30
|
+
end
|
31
|
+
|
32
|
+
module ConnectionManagement
|
33
|
+
def establish_connection(*args)
|
34
|
+
self.connection = connection_class.new(*args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
extend ConnectionManagement
|
38
|
+
|
39
|
+
module Naming
|
40
|
+
def column_family=(column_family)
|
41
|
+
@column_family = column_family
|
42
|
+
end
|
43
|
+
|
44
|
+
def column_family
|
45
|
+
@column_family || name.pluralize
|
46
|
+
end
|
47
|
+
end
|
48
|
+
extend Naming
|
49
|
+
|
50
|
+
extend ActiveModel::Naming
|
51
|
+
|
52
|
+
module ConfigurationDumper
|
53
|
+
def storage_config_xml
|
54
|
+
subclasses.map(&:constantize).map(&:column_family_configuration).flatten.map do |config|
|
55
|
+
config_to_xml(config)
|
56
|
+
end.join("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def config_to_xml(config)
|
60
|
+
xml = "<ColumnFamily "
|
61
|
+
config.each do |(attr_name, attr_value)|
|
62
|
+
xml << " #{attr_name}=\"#{attr_value}\""
|
63
|
+
end
|
64
|
+
xml << " />"
|
65
|
+
xml
|
66
|
+
end
|
67
|
+
end
|
68
|
+
extend ConfigurationDumper
|
69
|
+
|
70
|
+
include Callbacks
|
71
|
+
include Identity
|
72
|
+
include Attributes
|
73
|
+
include Persistence
|
74
|
+
include Indexes
|
75
|
+
include Dirty
|
76
|
+
|
77
|
+
include Validation
|
78
|
+
include Associations
|
79
|
+
|
80
|
+
attr_reader :attributes
|
81
|
+
attr_accessor :key
|
82
|
+
|
83
|
+
include Serialization
|
84
|
+
include Migrations
|
85
|
+
include Mocking
|
86
|
+
|
87
|
+
def initialize(attributes={})
|
88
|
+
@key = attributes.delete(:key)
|
89
|
+
@new_record = true
|
90
|
+
@attributes = {}.with_indifferent_access
|
91
|
+
self.attributes = attributes
|
92
|
+
@schema_version = self.class.current_schema_version
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
require 'cassandra_object/type_registration'
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class Cursor
|
3
|
+
def initialize(target_class, column_family, key, super_column, options={})
|
4
|
+
@target_class = target_class
|
5
|
+
@column_family = column_family
|
6
|
+
@key = key.to_s
|
7
|
+
@super_column = super_column
|
8
|
+
@options = options
|
9
|
+
@validators = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def find(number_to_find)
|
13
|
+
limit = number_to_find
|
14
|
+
objects = CassandraObject::Collection.new
|
15
|
+
out_of_keys = false
|
16
|
+
|
17
|
+
if start_with = @options[:start_after]
|
18
|
+
limit += 1
|
19
|
+
else
|
20
|
+
start_with = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
while objects.size < number_to_find && !out_of_keys
|
24
|
+
index_results = connection.get(@column_family, @key, @super_column, :count=>limit,
|
25
|
+
:start=>start_with,
|
26
|
+
:reversed=>@options[:reversed])
|
27
|
+
|
28
|
+
out_of_keys = index_results.size < limit
|
29
|
+
|
30
|
+
if !start_with.blank?
|
31
|
+
index_results.delete(start_with)
|
32
|
+
end
|
33
|
+
|
34
|
+
keys = index_results.keys
|
35
|
+
values = index_results.values
|
36
|
+
|
37
|
+
missing_keys = []
|
38
|
+
|
39
|
+
results = values.empty? ? {} : @target_class.multi_get(values)
|
40
|
+
results.each do |(key, result)|
|
41
|
+
if result.nil?
|
42
|
+
missing_keys << key
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
unless missing_keys.empty?
|
47
|
+
@target_class.multi_get(missing_keys, :quorum=>true).each do |(key, result)|
|
48
|
+
index_key = index_results.index(key)
|
49
|
+
if result.nil?
|
50
|
+
remove(index_key)
|
51
|
+
results.delete(key)
|
52
|
+
else
|
53
|
+
results[key] = result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
results.values.each do |o|
|
59
|
+
if @validators.all? {|v| v.call(o) }
|
60
|
+
objects << o
|
61
|
+
else
|
62
|
+
remove(index_results.index(o.key))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
start_with = objects.last_column_name = keys.last
|
67
|
+
limit = (number_to_find - results.size) + 1
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
return objects
|
72
|
+
end
|
73
|
+
|
74
|
+
def connection
|
75
|
+
@target_class.connection
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove(index_key)
|
79
|
+
connection.remove(@column_family, @key, @super_column, index_key)
|
80
|
+
end
|
81
|
+
|
82
|
+
def validator(&validator)
|
83
|
+
@validators << validator
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Dirty
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::Dirty
|
5
|
+
|
6
|
+
module InstanceMethods
|
7
|
+
def attributes_changed!(attributes)
|
8
|
+
attributes.each do |attr_name|
|
9
|
+
attribute_will_change!(attr_name)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def save
|
14
|
+
super.tap { changed_attributes.clear }
|
15
|
+
end
|
16
|
+
|
17
|
+
def write_attribute(name, value)
|
18
|
+
name = name.to_s
|
19
|
+
unless attribute_changed?(name)
|
20
|
+
old = read_attribute(name)
|
21
|
+
changed_attributes[name] = old if old != value
|
22
|
+
end
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'cassandra_object/identity/abstract_key_factory'
|
2
|
+
require 'cassandra_object/identity/key'
|
3
|
+
require 'cassandra_object/identity/uuid_key_factory'
|
4
|
+
require 'cassandra_object/identity/natural_key_factory'
|
5
|
+
|
6
|
+
module CassandraObject
|
7
|
+
# Some docs will be needed here but the gist of this is simple. Instead of returning a string, Base#key now returns a key object.
|
8
|
+
# There are corresponding key factories which generate them
|
9
|
+
module Identity
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
module ClassMethods
|
12
|
+
# Indicate what kind of key the model will have: uuid or natural
|
13
|
+
#
|
14
|
+
# @param [:uuid, :natural] the type of key
|
15
|
+
# @param the options you want to pass along to the key factory (like :attributes => :name, for a natural key).
|
16
|
+
#
|
17
|
+
def key(name_or_factory = :uuid, *options)
|
18
|
+
@key_factory = case name_or_factory
|
19
|
+
when :uuid
|
20
|
+
UUIDKeyFactory.new
|
21
|
+
when :natural
|
22
|
+
NaturalKeyFactory.new(*options)
|
23
|
+
else
|
24
|
+
name_or_factory
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_key(object = nil)
|
29
|
+
@key_factory.next_key(object).tap do |key|
|
30
|
+
raise "Keys may not be nil" if key.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_key(string)
|
35
|
+
@key_factory.parse(string)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
|
41
|
+
def ==(comparison_object)
|
42
|
+
comparison_object.equal?(self) ||
|
43
|
+
(comparison_object.instance_of?(self.class) &&
|
44
|
+
comparison_object.key == key &&
|
45
|
+
!comparison_object.new_record?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def eql?(comparison_object)
|
49
|
+
self == (comparison_object)
|
50
|
+
end
|
51
|
+
|
52
|
+
def hash
|
53
|
+
key.to_s.hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_param
|
57
|
+
key.to_param
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|