extendi-cassandra_object 1.0.0
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.
- 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
|