extendi-cassandra_object 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +23 -0
  4. data/CHANGELOG +0 -0
  5. data/Gemfile +17 -0
  6. data/LICENSE +13 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +177 -0
  9. data/Rakefile +12 -0
  10. data/extendi-cassandra_object.gemspec +26 -0
  11. data/lib/cassandra_object.rb +73 -0
  12. data/lib/cassandra_object/adapters/abstract_adapter.rb +61 -0
  13. data/lib/cassandra_object/adapters/cassandra_adapter.rb +269 -0
  14. data/lib/cassandra_object/adapters/cassandra_schemaless_adapter.rb +306 -0
  15. data/lib/cassandra_object/attribute_methods.rb +96 -0
  16. data/lib/cassandra_object/attribute_methods/definition.rb +22 -0
  17. data/lib/cassandra_object/attribute_methods/dirty.rb +36 -0
  18. data/lib/cassandra_object/attribute_methods/primary_key.rb +25 -0
  19. data/lib/cassandra_object/attribute_methods/typecasting.rb +59 -0
  20. data/lib/cassandra_object/base.rb +33 -0
  21. data/lib/cassandra_object/base_schema.rb +11 -0
  22. data/lib/cassandra_object/base_schemaless.rb +11 -0
  23. data/lib/cassandra_object/base_schemaless_dynamic.rb +11 -0
  24. data/lib/cassandra_object/belongs_to.rb +63 -0
  25. data/lib/cassandra_object/belongs_to/association.rb +49 -0
  26. data/lib/cassandra_object/belongs_to/builder.rb +40 -0
  27. data/lib/cassandra_object/belongs_to/reflection.rb +30 -0
  28. data/lib/cassandra_object/callbacks.rb +29 -0
  29. data/lib/cassandra_object/core.rb +63 -0
  30. data/lib/cassandra_object/errors.rb +10 -0
  31. data/lib/cassandra_object/identity.rb +26 -0
  32. data/lib/cassandra_object/inspect.rb +25 -0
  33. data/lib/cassandra_object/log_subscriber.rb +44 -0
  34. data/lib/cassandra_object/model.rb +60 -0
  35. data/lib/cassandra_object/persistence.rb +203 -0
  36. data/lib/cassandra_object/railtie.rb +33 -0
  37. data/lib/cassandra_object/railties/controller_runtime.rb +45 -0
  38. data/lib/cassandra_object/schema.rb +83 -0
  39. data/lib/cassandra_object/schemaless.rb +83 -0
  40. data/lib/cassandra_object/scope.rb +86 -0
  41. data/lib/cassandra_object/scope/finder_methods.rb +54 -0
  42. data/lib/cassandra_object/scope/query_methods.rb +69 -0
  43. data/lib/cassandra_object/scoping.rb +27 -0
  44. data/lib/cassandra_object/serialization.rb +6 -0
  45. data/lib/cassandra_object/tasks/ks.rake +54 -0
  46. data/lib/cassandra_object/timestamps.rb +19 -0
  47. data/lib/cassandra_object/type.rb +16 -0
  48. data/lib/cassandra_object/types.rb +8 -0
  49. data/lib/cassandra_object/types/array_type.rb +16 -0
  50. data/lib/cassandra_object/types/base_type.rb +26 -0
  51. data/lib/cassandra_object/types/boolean_type.rb +20 -0
  52. data/lib/cassandra_object/types/date_type.rb +22 -0
  53. data/lib/cassandra_object/types/float_type.rb +16 -0
  54. data/lib/cassandra_object/types/integer_type.rb +20 -0
  55. data/lib/cassandra_object/types/json_type.rb +13 -0
  56. data/lib/cassandra_object/types/string_type.rb +19 -0
  57. data/lib/cassandra_object/types/time_type.rb +16 -0
  58. data/lib/cassandra_object/types/type_helper.rb +39 -0
  59. data/lib/cassandra_object/validations.rb +44 -0
  60. data/test/support/cassandra.rb +63 -0
  61. data/test/support/issue.rb +12 -0
  62. data/test/support/issue_dynamic.rb +12 -0
  63. data/test/support/issue_schema.rb +17 -0
  64. data/test/support/issue_schema_child.rb +17 -0
  65. data/test/support/issue_schema_father.rb +13 -0
  66. data/test/test_helper.rb +41 -0
  67. data/test/unit/active_model_test.rb +18 -0
  68. data/test/unit/adapters/adapter_test.rb +6 -0
  69. data/test/unit/attribute_methods/definition_test.rb +13 -0
  70. data/test/unit/attribute_methods/dirty_test.rb +72 -0
  71. data/test/unit/attribute_methods/primary_key_test.rb +26 -0
  72. data/test/unit/attribute_methods/typecasting_test.rb +119 -0
  73. data/test/unit/attribute_methods_test.rb +51 -0
  74. data/test/unit/base_test.rb +20 -0
  75. data/test/unit/belongs_to/reflection_test.rb +12 -0
  76. data/test/unit/belongs_to_test.rb +63 -0
  77. data/test/unit/callbacks_test.rb +46 -0
  78. data/test/unit/connection_test.rb +6 -0
  79. data/test/unit/connections/connections_test.rb +55 -0
  80. data/test/unit/core_test.rb +55 -0
  81. data/test/unit/identity_test.rb +26 -0
  82. data/test/unit/inspect_test.rb +26 -0
  83. data/test/unit/log_subscriber_test.rb +25 -0
  84. data/test/unit/persistence_schema_test.rb +156 -0
  85. data/test/unit/persistence_test.rb +266 -0
  86. data/test/unit/railties/controller_runtime_test.rb +48 -0
  87. data/test/unit/schema/tasks_test.rb +32 -0
  88. data/test/unit/schema_test.rb +115 -0
  89. data/test/unit/schemaless_test.rb +100 -0
  90. data/test/unit/scope/finder_methods_test.rb +117 -0
  91. data/test/unit/scope/query_methods_test.rb +32 -0
  92. data/test/unit/scoping_test.rb +7 -0
  93. data/test/unit/timestamps_test.rb +27 -0
  94. data/test/unit/types/array_type_test.rb +17 -0
  95. data/test/unit/types/base_type_test.rb +19 -0
  96. data/test/unit/types/boolean_type_test.rb +24 -0
  97. data/test/unit/types/date_type_test.rb +15 -0
  98. data/test/unit/types/float_type_test.rb +17 -0
  99. data/test/unit/types/integer_type_test.rb +19 -0
  100. data/test/unit/types/json_type_test.rb +23 -0
  101. data/test/unit/types/string_type_test.rb +25 -0
  102. data/test/unit/types/time_type_test.rb +14 -0
  103. data/test/unit/validations_test.rb +27 -0
  104. 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,10 @@
1
+ module CassandraObject
2
+ class CasssandraObjectError < StandardError
3
+ end
4
+
5
+ class RecordNotSaved < CasssandraObjectError
6
+ end
7
+
8
+ class RecordNotFound < CasssandraObjectError
9
+ end
10
+ 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