extendi-cassandra_object 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +23 -0
- data/CHANGELOG +0 -0
- data/Gemfile +17 -0
- data/LICENSE +13 -0
- data/MIT-LICENSE +20 -0
- data/README.md +177 -0
- data/Rakefile +12 -0
- data/extendi-cassandra_object.gemspec +26 -0
- data/lib/cassandra_object.rb +73 -0
- data/lib/cassandra_object/adapters/abstract_adapter.rb +61 -0
- data/lib/cassandra_object/adapters/cassandra_adapter.rb +269 -0
- data/lib/cassandra_object/adapters/cassandra_schemaless_adapter.rb +306 -0
- data/lib/cassandra_object/attribute_methods.rb +96 -0
- data/lib/cassandra_object/attribute_methods/definition.rb +22 -0
- data/lib/cassandra_object/attribute_methods/dirty.rb +36 -0
- data/lib/cassandra_object/attribute_methods/primary_key.rb +25 -0
- data/lib/cassandra_object/attribute_methods/typecasting.rb +59 -0
- data/lib/cassandra_object/base.rb +33 -0
- data/lib/cassandra_object/base_schema.rb +11 -0
- data/lib/cassandra_object/base_schemaless.rb +11 -0
- data/lib/cassandra_object/base_schemaless_dynamic.rb +11 -0
- data/lib/cassandra_object/belongs_to.rb +63 -0
- data/lib/cassandra_object/belongs_to/association.rb +49 -0
- data/lib/cassandra_object/belongs_to/builder.rb +40 -0
- data/lib/cassandra_object/belongs_to/reflection.rb +30 -0
- data/lib/cassandra_object/callbacks.rb +29 -0
- data/lib/cassandra_object/core.rb +63 -0
- data/lib/cassandra_object/errors.rb +10 -0
- data/lib/cassandra_object/identity.rb +26 -0
- data/lib/cassandra_object/inspect.rb +25 -0
- data/lib/cassandra_object/log_subscriber.rb +44 -0
- data/lib/cassandra_object/model.rb +60 -0
- data/lib/cassandra_object/persistence.rb +203 -0
- data/lib/cassandra_object/railtie.rb +33 -0
- data/lib/cassandra_object/railties/controller_runtime.rb +45 -0
- data/lib/cassandra_object/schema.rb +83 -0
- data/lib/cassandra_object/schemaless.rb +83 -0
- data/lib/cassandra_object/scope.rb +86 -0
- data/lib/cassandra_object/scope/finder_methods.rb +54 -0
- data/lib/cassandra_object/scope/query_methods.rb +69 -0
- data/lib/cassandra_object/scoping.rb +27 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/tasks/ks.rake +54 -0
- data/lib/cassandra_object/timestamps.rb +19 -0
- data/lib/cassandra_object/type.rb +16 -0
- data/lib/cassandra_object/types.rb +8 -0
- data/lib/cassandra_object/types/array_type.rb +16 -0
- data/lib/cassandra_object/types/base_type.rb +26 -0
- data/lib/cassandra_object/types/boolean_type.rb +20 -0
- data/lib/cassandra_object/types/date_type.rb +22 -0
- data/lib/cassandra_object/types/float_type.rb +16 -0
- data/lib/cassandra_object/types/integer_type.rb +20 -0
- data/lib/cassandra_object/types/json_type.rb +13 -0
- data/lib/cassandra_object/types/string_type.rb +19 -0
- data/lib/cassandra_object/types/time_type.rb +16 -0
- data/lib/cassandra_object/types/type_helper.rb +39 -0
- data/lib/cassandra_object/validations.rb +44 -0
- data/test/support/cassandra.rb +63 -0
- data/test/support/issue.rb +12 -0
- data/test/support/issue_dynamic.rb +12 -0
- data/test/support/issue_schema.rb +17 -0
- data/test/support/issue_schema_child.rb +17 -0
- data/test/support/issue_schema_father.rb +13 -0
- data/test/test_helper.rb +41 -0
- data/test/unit/active_model_test.rb +18 -0
- data/test/unit/adapters/adapter_test.rb +6 -0
- data/test/unit/attribute_methods/definition_test.rb +13 -0
- data/test/unit/attribute_methods/dirty_test.rb +72 -0
- data/test/unit/attribute_methods/primary_key_test.rb +26 -0
- data/test/unit/attribute_methods/typecasting_test.rb +119 -0
- data/test/unit/attribute_methods_test.rb +51 -0
- data/test/unit/base_test.rb +20 -0
- data/test/unit/belongs_to/reflection_test.rb +12 -0
- data/test/unit/belongs_to_test.rb +63 -0
- data/test/unit/callbacks_test.rb +46 -0
- data/test/unit/connection_test.rb +6 -0
- data/test/unit/connections/connections_test.rb +55 -0
- data/test/unit/core_test.rb +55 -0
- data/test/unit/identity_test.rb +26 -0
- data/test/unit/inspect_test.rb +26 -0
- data/test/unit/log_subscriber_test.rb +25 -0
- data/test/unit/persistence_schema_test.rb +156 -0
- data/test/unit/persistence_test.rb +266 -0
- data/test/unit/railties/controller_runtime_test.rb +48 -0
- data/test/unit/schema/tasks_test.rb +32 -0
- data/test/unit/schema_test.rb +115 -0
- data/test/unit/schemaless_test.rb +100 -0
- data/test/unit/scope/finder_methods_test.rb +117 -0
- data/test/unit/scope/query_methods_test.rb +32 -0
- data/test/unit/scoping_test.rb +7 -0
- data/test/unit/timestamps_test.rb +27 -0
- data/test/unit/types/array_type_test.rb +17 -0
- data/test/unit/types/base_type_test.rb +19 -0
- data/test/unit/types/boolean_type_test.rb +24 -0
- data/test/unit/types/date_type_test.rb +15 -0
- data/test/unit/types/float_type_test.rb +17 -0
- data/test/unit/types/integer_type_test.rb +19 -0
- data/test/unit/types/json_type_test.rb +23 -0
- data/test/unit/types/string_type_test.rb +25 -0
- data/test/unit/types/time_type_test.rb +14 -0
- data/test/unit/validations_test.rb +27 -0
- metadata +202 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module BelongsTo
|
3
|
+
class Reflection
|
4
|
+
attr_reader :model, :name, :options
|
5
|
+
def initialize(model, name, options)
|
6
|
+
@model, @name, @options = model, name, options
|
7
|
+
end
|
8
|
+
|
9
|
+
def instance_variable_name
|
10
|
+
"@#{name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def foreign_key
|
14
|
+
"#{name}_id"
|
15
|
+
end
|
16
|
+
|
17
|
+
def polymorphic_column
|
18
|
+
"#{name}_type"
|
19
|
+
end
|
20
|
+
|
21
|
+
def polymorphic?
|
22
|
+
options[:polymorphic]
|
23
|
+
end
|
24
|
+
|
25
|
+
def class_name
|
26
|
+
options[:class_name] || name.to_s.camelize
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Callbacks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
extend ActiveModel::Callbacks
|
7
|
+
include ActiveModel::Validations::Callbacks
|
8
|
+
|
9
|
+
define_model_callbacks :save, :create, :update, :destroy
|
10
|
+
end
|
11
|
+
|
12
|
+
def destroy #:nodoc:
|
13
|
+
run_callbacks(:destroy) { super }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def write(*args) #:nodoc:
|
18
|
+
run_callbacks(:save) { super }
|
19
|
+
end
|
20
|
+
|
21
|
+
def create #:nodoc:
|
22
|
+
run_callbacks(:create) { super }
|
23
|
+
end
|
24
|
+
|
25
|
+
def update(*) #:nodoc:
|
26
|
+
run_callbacks(:update) { super }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Core
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def initialize(attributes=nil)
|
6
|
+
@new_record = true
|
7
|
+
@destroyed = false
|
8
|
+
@attributes = {}
|
9
|
+
self.attributes = attributes || {}
|
10
|
+
attribute_definitions.each_value do |definition|
|
11
|
+
unless definition.default.nil? || attribute_exists?(definition.name)
|
12
|
+
@attributes[definition.name] = definition.default
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_dup(other)
|
20
|
+
@attributes = other.attributes
|
21
|
+
@attributes['created_at'] = nil
|
22
|
+
@attributes['updated_at'] = nil
|
23
|
+
@attributes.delete(self.class.primary_key)
|
24
|
+
@id = nil
|
25
|
+
@new_record = true
|
26
|
+
@destroyed = false
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_param
|
31
|
+
id
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_cql_response
|
35
|
+
self.class.cql_response.find(self.id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def hash
|
39
|
+
id.hash
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
def inspect
|
44
|
+
if self == Base
|
45
|
+
super
|
46
|
+
else
|
47
|
+
attr_list = @attributes.map do |col, definition| "#{col}: #{definition.type}" end * ', '
|
48
|
+
"#{super}(#{attr_list.truncate(140 * 1.7337)})"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def ==(comparison_object)
|
54
|
+
comparison_object.equal?(self) ||
|
55
|
+
(comparison_object.instance_of?(self.class) &&
|
56
|
+
comparison_object.id == id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def eql?(comparison_object)
|
60
|
+
self == (comparison_object)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module CassandraObject
|
4
|
+
module Identity
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :key_generator
|
9
|
+
|
10
|
+
key do
|
11
|
+
SecureRandom.uuid.tr('-','')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# Define a key generator. Default is UUID.
|
17
|
+
def key(&block)
|
18
|
+
self.key_generator = block
|
19
|
+
end
|
20
|
+
|
21
|
+
def _generate_key(object)
|
22
|
+
object.instance_eval(&key_generator)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Inspect
|
3
|
+
def inspect
|
4
|
+
inspection = ["#{self.class.primary_key}: #{id.inspect}"]
|
5
|
+
|
6
|
+
@attributes.each do |attribute, value|
|
7
|
+
if value.present?
|
8
|
+
inspection << "#{attribute}: #{attribute_for_inspect(value)}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
"#<#{self.class} #{inspection * ', '}>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def attribute_for_inspect(value)
|
16
|
+
if value.is_a?(String) && value.length > 50
|
17
|
+
"#{value[0..50]}...".inspect
|
18
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
19
|
+
%("#{value.to_s(:db)}")
|
20
|
+
else
|
21
|
+
value.inspect
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def self.runtime=(value)
|
4
|
+
Thread.current["cassandra_object_request_runtime"] = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.runtime
|
8
|
+
Thread.current["cassandra_object_request_runtime"] ||= 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.reset_runtime
|
12
|
+
rt, self.runtime = runtime, 0
|
13
|
+
rt
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
super
|
18
|
+
@odd_or_even = false
|
19
|
+
end
|
20
|
+
|
21
|
+
def cql(event)
|
22
|
+
self.class.runtime += event.duration
|
23
|
+
|
24
|
+
payload = event.payload
|
25
|
+
name = '%s (%.1fms)' % [payload[:name], event.duration]
|
26
|
+
cql = payload[:cql].squeeze(' ')
|
27
|
+
|
28
|
+
if odd?
|
29
|
+
name = color(name, CYAN, true)
|
30
|
+
cql = color(cql, nil, true)
|
31
|
+
else
|
32
|
+
name = color(name, MAGENTA, true)
|
33
|
+
end
|
34
|
+
|
35
|
+
debug " #{name} #{cql}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def odd?
|
39
|
+
@odd_or_even = !@odd_or_even
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
CassandraObject::LogSubscriber.attach_to :cassandra_object
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Model
|
3
|
+
def column_family=(column_family)
|
4
|
+
@column_family = column_family
|
5
|
+
end
|
6
|
+
|
7
|
+
def column_family
|
8
|
+
@column_family ||= base_class.name.pluralize
|
9
|
+
end
|
10
|
+
|
11
|
+
def base_class
|
12
|
+
class_of_active_record_descendant(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def config=(config)
|
16
|
+
@@config = config.deep_symbolize_keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def config
|
20
|
+
@@config
|
21
|
+
end
|
22
|
+
|
23
|
+
def allow_filtering=(value)
|
24
|
+
@allow_filtering = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def allow_filtering
|
28
|
+
@allow_filtering ||= false
|
29
|
+
end
|
30
|
+
|
31
|
+
def _key
|
32
|
+
# todo only first key
|
33
|
+
keys.tr('()','').split(',').first
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys=(value)
|
37
|
+
@keys = value
|
38
|
+
end
|
39
|
+
|
40
|
+
def keys
|
41
|
+
@keys ||= '(key)'
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Returns the class descending directly from ActiveRecord::Base or an
|
47
|
+
# abstract class, if any, in the inheritance hierarchy.
|
48
|
+
def class_of_active_record_descendant(klass)
|
49
|
+
# klass
|
50
|
+
|
51
|
+
if (klass == Base || klass.superclass == Base) || (klass == BaseSchemaless || klass.superclass == BaseSchemaless) || (klass == BaseSchema || klass.superclass == BaseSchema) || (klass == BaseSchemalessDynamic || klass.superclass == BaseSchemalessDynamic)
|
52
|
+
klass
|
53
|
+
elsif klass.superclass.nil?
|
54
|
+
raise "#{name} doesn't belong in a hierarchy descending from CassandraObject"
|
55
|
+
else
|
56
|
+
class_of_active_record_descendant(klass.superclass)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Persistence
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :batch_statements
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def ttl=(value)
|
12
|
+
@ttl = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def ttl
|
16
|
+
@ttl ||= nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def remove(ids)
|
20
|
+
delete ids
|
21
|
+
end
|
22
|
+
|
23
|
+
def _key
|
24
|
+
# todo only mono primary id for now
|
25
|
+
self.keys.tr('()','').split(',').first
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete_all
|
29
|
+
adapter.execute "TRUNCATE #{column_family}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def create(attributes = {}, &block)
|
33
|
+
self.ttl = attributes.delete(:ttl)
|
34
|
+
if self.schema_type != :dynamic_attributes
|
35
|
+
new(attributes, &block).tap do |object|
|
36
|
+
object.save
|
37
|
+
end
|
38
|
+
else
|
39
|
+
key = attributes[:key]
|
40
|
+
insert_record key.to_s, attributes.except(:key).stringify_keys
|
41
|
+
attributes
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def update(id, attributes)
|
46
|
+
update_record(id, attributes)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete(ids, attributes = [])
|
50
|
+
ids = [ids] if !ids.is_a?(Array)
|
51
|
+
if !attributes.empty?
|
52
|
+
attr = {}
|
53
|
+
attributes.each{|a| attr[a] = nil}
|
54
|
+
ids.each do |id|
|
55
|
+
adapter.update column_family, id, encode_attributes(attr)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
if self.schema_type == :standard
|
59
|
+
adapter.delete column_family, self._key, ids
|
60
|
+
else
|
61
|
+
adapter.delete column_family, ids
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def insert_record(id, attributes)
|
67
|
+
attributes[self._key] = id if self.schema_type == :standard
|
68
|
+
adapter.insert column_family, id, encode_attributes(attributes), self.ttl
|
69
|
+
end
|
70
|
+
|
71
|
+
def update_record(id, attributes)
|
72
|
+
return if attributes.empty?
|
73
|
+
if self.schema_type == :standard
|
74
|
+
attributes[self._key] = id
|
75
|
+
id = self._key
|
76
|
+
end
|
77
|
+
adapter.update column_family, id, encode_attributes(attributes), self.ttl
|
78
|
+
end
|
79
|
+
|
80
|
+
def batching?
|
81
|
+
adapter.batching?
|
82
|
+
end
|
83
|
+
|
84
|
+
def batch(&block)
|
85
|
+
adapter.batch(&block)
|
86
|
+
end
|
87
|
+
|
88
|
+
def instantiate(id, attributes)
|
89
|
+
allocate.tap do |object|
|
90
|
+
object.instance_variable_set('@id', id) if id
|
91
|
+
object.instance_variable_set('@new_record', false)
|
92
|
+
object.instance_variable_set('@destroyed', false)
|
93
|
+
object.instance_variable_set('@attributes', typecast_persisted_attributes(object, attributes))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def encode_attributes(attributes)
|
98
|
+
encoded = {}
|
99
|
+
attributes.each do |column_name, value|
|
100
|
+
if value.nil?
|
101
|
+
encoded[column_name] = nil
|
102
|
+
else
|
103
|
+
if self.schema_type == :dynamic_attributes
|
104
|
+
encoded[column_name] = value.to_s
|
105
|
+
elsif self.schema_type == :standard
|
106
|
+
encoded[column_name] = value
|
107
|
+
else
|
108
|
+
encoded[column_name] = attribute_definitions[column_name].coder.encode(value)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
encoded
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def typecast_persisted_attributes(object, attributes)
|
118
|
+
attributes.each do |key, value|
|
119
|
+
if definition = attribute_definitions[key.to_s]
|
120
|
+
attributes[key] = definition.instantiate(object, value)
|
121
|
+
else
|
122
|
+
attributes.delete(key)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
attribute_definitions.each_value do |definition|
|
127
|
+
unless definition.default.nil? || attributes.has_key?(definition.name)
|
128
|
+
attributes[definition.name] = definition.default
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
attributes
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def new_record?
|
137
|
+
@new_record
|
138
|
+
end
|
139
|
+
|
140
|
+
def destroyed?
|
141
|
+
@destroyed
|
142
|
+
end
|
143
|
+
|
144
|
+
def persisted?
|
145
|
+
!(new_record? || destroyed?)
|
146
|
+
end
|
147
|
+
|
148
|
+
def save(*)
|
149
|
+
new_record? ? create : update
|
150
|
+
end
|
151
|
+
|
152
|
+
def destroy
|
153
|
+
self.class.remove(id)
|
154
|
+
@destroyed = true
|
155
|
+
end
|
156
|
+
|
157
|
+
def update_attribute(name, value)
|
158
|
+
name = name.to_s
|
159
|
+
send("#{name}=", value)
|
160
|
+
save(validate: false)
|
161
|
+
end
|
162
|
+
|
163
|
+
def update_attributes(attributes)
|
164
|
+
self.attributes = attributes
|
165
|
+
save
|
166
|
+
end
|
167
|
+
|
168
|
+
def update_attributes!(attributes)
|
169
|
+
self.attributes = attributes
|
170
|
+
save!
|
171
|
+
end
|
172
|
+
|
173
|
+
def becomes(klass)
|
174
|
+
became = klass.new
|
175
|
+
became.instance_variable_set('@attributes', @attributes)
|
176
|
+
became.instance_variable_set('@new_record', new_record?)
|
177
|
+
became.instance_variable_set('@destroyed', destroyed?)
|
178
|
+
became
|
179
|
+
end
|
180
|
+
|
181
|
+
def reload
|
182
|
+
clear_belongs_to_cache
|
183
|
+
@attributes = self.class.find(id).instance_variable_get('@attributes')
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def create
|
190
|
+
@new_record = false
|
191
|
+
write :insert_record
|
192
|
+
end
|
193
|
+
|
194
|
+
def update
|
195
|
+
write :update_record
|
196
|
+
end
|
197
|
+
|
198
|
+
def write(method)
|
199
|
+
changed_attributes = Hash[changed.map { |attr| [attr, read_attribute(attr)] }]
|
200
|
+
self.class.send(method, id, changed_attributes)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|