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.
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