cassandra_object 0.6.0.pre
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/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/associations.rb +35 -0
- data/lib/cassandra_object/attributes.rb +93 -0
- data/lib/cassandra_object/base.rb +104 -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/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/identity.rb +61 -0
- data/lib/cassandra_object/indexes.rb +129 -0
- data/lib/cassandra_object/legacy_callbacks.rb +33 -0
- data/lib/cassandra_object/migrations.rb +72 -0
- data/lib/cassandra_object/mocking.rb +15 -0
- data/lib/cassandra_object/persistence.rb +193 -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 +58 -0
- data/lib/cassandra_object.rb +30 -0
- data/vendor/active_support_shims.rb +4 -0
- data/vendor/activemodel/CHANGELOG +13 -0
- data/vendor/activemodel/CHANGES +12 -0
- data/vendor/activemodel/MIT-LICENSE +21 -0
- data/vendor/activemodel/README +21 -0
- data/vendor/activemodel/Rakefile +52 -0
- data/vendor/activemodel/activemodel.gemspec +19 -0
- data/vendor/activemodel/examples/validations.rb +29 -0
- data/vendor/activemodel/lib/active_model/attribute_methods.rb +291 -0
- data/vendor/activemodel/lib/active_model/callbacks.rb +91 -0
- data/vendor/activemodel/lib/active_model/conversion.rb +8 -0
- data/vendor/activemodel/lib/active_model/deprecated_error_methods.rb +33 -0
- data/vendor/activemodel/lib/active_model/dirty.rb +126 -0
- data/vendor/activemodel/lib/active_model/errors.rb +162 -0
- data/vendor/activemodel/lib/active_model/lint.rb +91 -0
- data/vendor/activemodel/lib/active_model/locale/en.yml +27 -0
- data/vendor/activemodel/lib/active_model/naming.rb +45 -0
- data/vendor/activemodel/lib/active_model/observing.rb +191 -0
- data/vendor/activemodel/lib/active_model/railtie.rb +2 -0
- data/vendor/activemodel/lib/active_model/serialization.rb +30 -0
- data/vendor/activemodel/lib/active_model/serializers/json.rb +96 -0
- data/vendor/activemodel/lib/active_model/serializers/xml.rb +204 -0
- data/vendor/activemodel/lib/active_model/state_machine/event.rb +62 -0
- data/vendor/activemodel/lib/active_model/state_machine/machine.rb +75 -0
- data/vendor/activemodel/lib/active_model/state_machine/state.rb +47 -0
- data/vendor/activemodel/lib/active_model/state_machine/state_transition.rb +40 -0
- data/vendor/activemodel/lib/active_model/state_machine.rb +70 -0
- data/vendor/activemodel/lib/active_model/test_case.rb +18 -0
- data/vendor/activemodel/lib/active_model/translation.rb +44 -0
- data/vendor/activemodel/lib/active_model/validations/acceptance.rb +55 -0
- data/vendor/activemodel/lib/active_model/validations/confirmation.rb +47 -0
- data/vendor/activemodel/lib/active_model/validations/exclusion.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/format.rb +64 -0
- data/vendor/activemodel/lib/active_model/validations/inclusion.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/length.rb +117 -0
- data/vendor/activemodel/lib/active_model/validations/numericality.rb +111 -0
- data/vendor/activemodel/lib/active_model/validations/presence.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/with.rb +59 -0
- data/vendor/activemodel/lib/active_model/validations.rb +120 -0
- data/vendor/activemodel/lib/active_model/validator.rb +110 -0
- data/vendor/activemodel/lib/active_model/version.rb +9 -0
- data/vendor/activemodel/lib/active_model.rb +61 -0
- data/vendor/activemodel/test/cases/attribute_methods_test.rb +46 -0
- data/vendor/activemodel/test/cases/callbacks_test.rb +70 -0
- data/vendor/activemodel/test/cases/helper.rb +23 -0
- data/vendor/activemodel/test/cases/lint_test.rb +28 -0
- data/vendor/activemodel/test/cases/naming_test.rb +28 -0
- data/vendor/activemodel/test/cases/observing_test.rb +133 -0
- data/vendor/activemodel/test/cases/serializeration/json_serialization_test.rb +83 -0
- data/vendor/activemodel/test/cases/serializeration/xml_serialization_test.rb +110 -0
- data/vendor/activemodel/test/cases/state_machine/event_test.rb +49 -0
- data/vendor/activemodel/test/cases/state_machine/machine_test.rb +43 -0
- data/vendor/activemodel/test/cases/state_machine/state_test.rb +72 -0
- data/vendor/activemodel/test/cases/state_machine/state_transition_test.rb +84 -0
- data/vendor/activemodel/test/cases/state_machine_test.rb +312 -0
- data/vendor/activemodel/test/cases/tests_database.rb +37 -0
- data/vendor/activemodel/test/cases/translation_test.rb +45 -0
- data/vendor/activemodel/test/cases/validations/acceptance_validation_test.rb +71 -0
- data/vendor/activemodel/test/cases/validations/conditional_validation_test.rb +141 -0
- data/vendor/activemodel/test/cases/validations/confirmation_validation_test.rb +58 -0
- data/vendor/activemodel/test/cases/validations/exclusion_validation_test.rb +47 -0
- data/vendor/activemodel/test/cases/validations/format_validation_test.rb +118 -0
- data/vendor/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +175 -0
- data/vendor/activemodel/test/cases/validations/i18n_validation_test.rb +527 -0
- data/vendor/activemodel/test/cases/validations/inclusion_validation_test.rb +71 -0
- data/vendor/activemodel/test/cases/validations/length_validation_test.rb +437 -0
- data/vendor/activemodel/test/cases/validations/numericality_validation_test.rb +180 -0
- data/vendor/activemodel/test/cases/validations/presence_validation_test.rb +70 -0
- data/vendor/activemodel/test/cases/validations/with_validation_test.rb +166 -0
- data/vendor/activemodel/test/cases/validations_test.rb +215 -0
- data/vendor/activemodel/test/config.rb +3 -0
- data/vendor/activemodel/test/fixtures/topics.yml +41 -0
- data/vendor/activemodel/test/models/contact.rb +7 -0
- data/vendor/activemodel/test/models/custom_reader.rb +17 -0
- data/vendor/activemodel/test/models/developer.rb +6 -0
- data/vendor/activemodel/test/models/person.rb +9 -0
- data/vendor/activemodel/test/models/reply.rb +34 -0
- data/vendor/activemodel/test/models/topic.rb +9 -0
- data/vendor/activemodel/test/models/track_back.rb +4 -0
- data/vendor/activemodel/test/schema.rb +14 -0
- data/vendor/activesupport/lib/active_support/autoload.rb +48 -0
- data/vendor/activesupport/lib/active_support/concern.rb +25 -0
- data/vendor/activesupport/lib/active_support/core_ext/array/wrap.rb +20 -0
- data/vendor/activesupport/lib/active_support/core_ext/object/blank.rb +58 -0
- data/vendor/activesupport/lib/active_support/core_ext/object/tap.rb +6 -0
- data/vendor/activesupport/lib/active_support/dependency_module.rb +17 -0
- data/vendor/activesupport/lib/active_support/i18n.rb +2 -0
- data/vendor/activesupport/lib/active_support/locale/en.yml +33 -0
- metadata +230 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
module CassandraObject
|
|
2
|
+
class OneToManyAssociation
|
|
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.singularize.camelize
|
|
7
|
+
@options = options
|
|
8
|
+
|
|
9
|
+
define_methods!
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def find(owner, options = {})
|
|
13
|
+
reversed = options.has_key?(:reversed) ? options[:reversed] : reversed?
|
|
14
|
+
cursor = CassandraObject::Cursor.new(target_class, column_family, owner.key.to_s, @association_name, :start_after => options[:start_after], :reversed => reversed)
|
|
15
|
+
cursor.find(options[:limit] || 100)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add(owner, record, set_inverse = true)
|
|
19
|
+
connection.insert(column_family, owner.key.to_s, {@association_name=>{new_key=>record.key.to_s}})
|
|
20
|
+
if has_inverse? && set_inverse
|
|
21
|
+
inverse.set_inverse(record, owner)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def new_key
|
|
26
|
+
SimpleUUID::UUID.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def column_family
|
|
30
|
+
@owner_class.to_s + "Relationships"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def connection
|
|
34
|
+
@owner_class.connection
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def target_class
|
|
38
|
+
@target_class ||= @target_class_name.constantize
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def new_proxy(owner)
|
|
42
|
+
OneToManyAssociationProxy.new(self, owner)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def has_inverse?
|
|
46
|
+
@options[:inverse_of]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def inverse
|
|
50
|
+
has_inverse? && target_class.associations[@options[:inverse_of]]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def set_inverse(owner, record)
|
|
54
|
+
add(owner, record, false)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def reversed?
|
|
58
|
+
@options[:reversed] == true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def define_methods!
|
|
62
|
+
@owner_class.class_eval <<-eos
|
|
63
|
+
def #{@association_name}
|
|
64
|
+
@_#{@association_name} ||= self.class.associations[:#{@association_name}].new_proxy(self)
|
|
65
|
+
end
|
|
66
|
+
eos
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class OneToManyAssociationProxy
|
|
71
|
+
def initialize(association, owner)
|
|
72
|
+
@association = association
|
|
73
|
+
@owner = owner
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
include Enumerable
|
|
77
|
+
def each
|
|
78
|
+
target.each do |i|
|
|
79
|
+
yield i
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def <<(record)
|
|
84
|
+
@association.add(@owner, record)
|
|
85
|
+
if loaded?
|
|
86
|
+
@target << record
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Get the targets of this association proxy
|
|
91
|
+
#
|
|
92
|
+
# @param [Hash] options the options with which to modify this query
|
|
93
|
+
# @option options [String] :start_after the key after which to start returning results
|
|
94
|
+
# @option options [Boolean] :reversed (false or association default) return the results in reverse order
|
|
95
|
+
# @option options [Integer] :limit the max number of results to return
|
|
96
|
+
# @return [Array<CassandraObject::Base>] an array of objects of type self#target_class
|
|
97
|
+
#
|
|
98
|
+
def all(options = {})
|
|
99
|
+
@association.find(@owner, options)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Create a record of the associated type with
|
|
103
|
+
# the supplied attributes and add it to this
|
|
104
|
+
# association
|
|
105
|
+
#
|
|
106
|
+
# @param [Hash] attributes the attributes with which to create the object
|
|
107
|
+
# @return [CassandraObject::Base] the newly created object
|
|
108
|
+
#
|
|
109
|
+
def create(attributes)
|
|
110
|
+
returning @association.target_class.create(attributes) do |record|
|
|
111
|
+
if record.valid?
|
|
112
|
+
self << record
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def create!(attributes)
|
|
118
|
+
returning @association.target_class.create!(attributes) do |record|
|
|
119
|
+
self << record
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def target
|
|
124
|
+
@target ||= begin
|
|
125
|
+
@loaded = true
|
|
126
|
+
@association.find(@owner)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
alias to_a target
|
|
131
|
+
|
|
132
|
+
def loaded?
|
|
133
|
+
defined?(@loaded) && @loaded
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -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,35 @@
|
|
|
1
|
+
require 'cassandra_object/associations/one_to_many'
|
|
2
|
+
require 'cassandra_object/associations/one_to_one'
|
|
3
|
+
|
|
4
|
+
module CassandraObject
|
|
5
|
+
module Associations
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
class_inheritable_hash :associations
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def column_family_configuration
|
|
14
|
+
super << {:Name=>"#{name}Relationships", :CompareWith=>"UTF8Type", :CompareSubcolumnsWith=>"TimeUUIDType", :ColumnType=>"Super"}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def association(association_name, options= {})
|
|
18
|
+
if options[:unique]
|
|
19
|
+
write_inheritable_hash(:associations, {association_name => OneToOneAssociation.new(association_name, self, options)})
|
|
20
|
+
else
|
|
21
|
+
write_inheritable_hash(:associations, {association_name => OneToManyAssociation.new(association_name, self, options)})
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def remove(key)
|
|
26
|
+
begin
|
|
27
|
+
connection.remove("#{name}Relationships", key.to_s)
|
|
28
|
+
rescue Cassandra::AccessError => e
|
|
29
|
+
raise e unless e.message =~ /Invalid column family/
|
|
30
|
+
end
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
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,104 @@
|
|
|
1
|
+
require 'cassandra'
|
|
2
|
+
require 'set'
|
|
3
|
+
require 'cassandra_object/attributes'
|
|
4
|
+
require 'cassandra_object/dirty'
|
|
5
|
+
require 'cassandra_object/persistence'
|
|
6
|
+
|
|
7
|
+
if CassandraObject.old_active_support
|
|
8
|
+
require 'cassandra_object/legacy_callbacks'
|
|
9
|
+
else
|
|
10
|
+
require 'cassandra_object/callbacks'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require 'cassandra_object/validation'
|
|
14
|
+
require 'cassandra_object/identity'
|
|
15
|
+
require 'cassandra_object/indexes'
|
|
16
|
+
require 'cassandra_object/serialization'
|
|
17
|
+
require 'cassandra_object/associations'
|
|
18
|
+
require 'cassandra_object/migrations'
|
|
19
|
+
require 'cassandra_object/cursor'
|
|
20
|
+
require 'cassandra_object/collection'
|
|
21
|
+
require 'cassandra_object/types'
|
|
22
|
+
require 'cassandra_object/mocking'
|
|
23
|
+
|
|
24
|
+
module CassandraObject
|
|
25
|
+
class Base
|
|
26
|
+
class_inheritable_accessor :connection
|
|
27
|
+
class_inheritable_writer :connection_class
|
|
28
|
+
|
|
29
|
+
def self.connection_class
|
|
30
|
+
read_inheritable_attribute(:connection_class) || Cassandra
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module ConnectionManagement
|
|
34
|
+
def establish_connection(*args)
|
|
35
|
+
self.connection = connection_class.new(*args)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
extend ConnectionManagement
|
|
39
|
+
|
|
40
|
+
module Naming
|
|
41
|
+
def column_family=(column_family)
|
|
42
|
+
@column_family = column_family
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def column_family
|
|
46
|
+
@column_family || name.pluralize
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
extend Naming
|
|
50
|
+
|
|
51
|
+
if CassandraObject.old_active_support
|
|
52
|
+
def self.lookup_ancestors
|
|
53
|
+
super.select { |x| x.model_name.present? }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
extend ActiveModel::Naming
|
|
58
|
+
|
|
59
|
+
module ConfigurationDumper
|
|
60
|
+
def storage_config_xml
|
|
61
|
+
subclasses.map(&:constantize).map(&:column_family_configuration).flatten.map do |config|
|
|
62
|
+
config_to_xml(config)
|
|
63
|
+
end.join("\n")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def config_to_xml(config)
|
|
67
|
+
xml = "<ColumnFamily "
|
|
68
|
+
config.each do |(attr_name, attr_value)|
|
|
69
|
+
xml << " #{attr_name}=\"#{attr_value}\""
|
|
70
|
+
end
|
|
71
|
+
xml << " />"
|
|
72
|
+
xml
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
extend ConfigurationDumper
|
|
76
|
+
|
|
77
|
+
include Callbacks
|
|
78
|
+
include Identity
|
|
79
|
+
include Attributes
|
|
80
|
+
include Persistence
|
|
81
|
+
include Indexes
|
|
82
|
+
include Dirty
|
|
83
|
+
|
|
84
|
+
include Validation
|
|
85
|
+
include Associations
|
|
86
|
+
|
|
87
|
+
attr_reader :attributes
|
|
88
|
+
attr_accessor :key
|
|
89
|
+
|
|
90
|
+
include Serialization
|
|
91
|
+
include Migrations
|
|
92
|
+
include Mocking
|
|
93
|
+
|
|
94
|
+
def initialize(attributes={})
|
|
95
|
+
@key = attributes.delete(:key)
|
|
96
|
+
@new_record = true
|
|
97
|
+
@attributes = {}.with_indifferent_access
|
|
98
|
+
self.attributes = attributes
|
|
99
|
+
@schema_version = self.class.current_schema_version
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
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,36 @@
|
|
|
1
|
+
module CassandraObject
|
|
2
|
+
module Identity
|
|
3
|
+
# Key factories need to support 3 operations
|
|
4
|
+
class AbstractKeyFactory
|
|
5
|
+
# Next key takes an object and returns the key object it should use.
|
|
6
|
+
# object will be ignored with synthetic keys but could be useful with natural ones
|
|
7
|
+
#
|
|
8
|
+
# @param [CassandraObject::Base] the object that needs a new key
|
|
9
|
+
# @return [CassandraObject::Identity::Key] the key
|
|
10
|
+
#
|
|
11
|
+
def next_key(object)
|
|
12
|
+
raise NotImplementedError, "#{self.class.name}#next_key isn't implemented."
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Parse should create a new key object from the 'to_param' format
|
|
16
|
+
#
|
|
17
|
+
# @param [String] the result of calling key.to_param
|
|
18
|
+
# @return [CassandraObject::Identity::Key] the parsed key
|
|
19
|
+
#
|
|
20
|
+
def parse(string)
|
|
21
|
+
raise NotImplementedError, "#{self.class.name}#parse isn't implemented."
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# create should create a new key object from the cassandra format.
|
|
26
|
+
#
|
|
27
|
+
# @param [String] the result of calling key.to_s
|
|
28
|
+
# @return [CassandraObject::Identity::Key] the key
|
|
29
|
+
#
|
|
30
|
+
def create(string)
|
|
31
|
+
raise NotImplementedError, "#{self.class.name}#create isn't implemented."
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module CassandraObject
|
|
2
|
+
module Identity
|
|
3
|
+
# An "interface" that keys need to implement
|
|
4
|
+
#
|
|
5
|
+
# You don't have to include this. But, there's no reason I can think of not to.
|
|
6
|
+
#
|
|
7
|
+
module Key
|
|
8
|
+
# to_param should return a nice-readable representation of the key suitable to chuck into URLs
|
|
9
|
+
#
|
|
10
|
+
# @return [String] a nice readable representation of the key suitable for URLs
|
|
11
|
+
def to_param; end
|
|
12
|
+
|
|
13
|
+
# to_s should return the bytes which will be written to cassandra both as keys and values for associations.
|
|
14
|
+
#
|
|
15
|
+
# @return [String] the bytes which will be written to cassandra as keys
|
|
16
|
+
def to_s; end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module CassandraObject
|
|
2
|
+
module Identity
|
|
3
|
+
class NaturalKeyFactory < AbstractKeyFactory
|
|
4
|
+
class NaturalKey
|
|
5
|
+
include Key
|
|
6
|
+
|
|
7
|
+
attr_reader :value
|
|
8
|
+
|
|
9
|
+
def initialize(value)
|
|
10
|
+
@value = value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_param
|
|
18
|
+
value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ==(other)
|
|
22
|
+
other.is_a?(NaturalKey) && other.value == value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def eql?(other)
|
|
26
|
+
other == self
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
attr_reader :attributes, :separator
|
|
31
|
+
|
|
32
|
+
def initialize(options)
|
|
33
|
+
@attributes = [*options[:attributes]]
|
|
34
|
+
@separator = options[:separator] || "-"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def next_key(object)
|
|
38
|
+
NaturalKey.new(attributes.map { |a| object.attributes[a.to_s] }.join(separator))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def parse(paramized_key)
|
|
42
|
+
NaturalKey.new(paramized_key)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create(paramized_key)
|
|
46
|
+
NaturalKey.new(paramized_key)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|