cassandra_object_rails 0.0.1

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 (97) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG +5 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE +13 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.rdoc +97 -0
  9. data/Rakefile +12 -0
  10. data/cassandra_object_rails.gemspec +26 -0
  11. data/lib/cassandra_object/attribute_methods.rb +87 -0
  12. data/lib/cassandra_object/attribute_methods/definition.rb +19 -0
  13. data/lib/cassandra_object/attribute_methods/dirty.rb +44 -0
  14. data/lib/cassandra_object/attribute_methods/primary_key.rb +25 -0
  15. data/lib/cassandra_object/attribute_methods/typecasting.rb +59 -0
  16. data/lib/cassandra_object/base.rb +69 -0
  17. data/lib/cassandra_object/belongs_to.rb +63 -0
  18. data/lib/cassandra_object/belongs_to/association.rb +48 -0
  19. data/lib/cassandra_object/belongs_to/builder.rb +40 -0
  20. data/lib/cassandra_object/belongs_to/reflection.rb +30 -0
  21. data/lib/cassandra_object/callbacks.rb +29 -0
  22. data/lib/cassandra_object/config.rb +15 -0
  23. data/lib/cassandra_object/connection.rb +36 -0
  24. data/lib/cassandra_object/consistency.rb +18 -0
  25. data/lib/cassandra_object/core.rb +59 -0
  26. data/lib/cassandra_object/errors.rb +6 -0
  27. data/lib/cassandra_object/identity.rb +24 -0
  28. data/lib/cassandra_object/inspect.rb +25 -0
  29. data/lib/cassandra_object/log_subscriber.rb +29 -0
  30. data/lib/cassandra_object/persistence.rb +169 -0
  31. data/lib/cassandra_object/rails_initializer.rb +19 -0
  32. data/lib/cassandra_object/railtie.rb +11 -0
  33. data/lib/cassandra_object/savepoints.rb +79 -0
  34. data/lib/cassandra_object/schema.rb +78 -0
  35. data/lib/cassandra_object/schema/tasks.rb +48 -0
  36. data/lib/cassandra_object/scope.rb +48 -0
  37. data/lib/cassandra_object/scope/batches.rb +32 -0
  38. data/lib/cassandra_object/scope/finder_methods.rb +47 -0
  39. data/lib/cassandra_object/scope/query_methods.rb +111 -0
  40. data/lib/cassandra_object/scoping.rb +19 -0
  41. data/lib/cassandra_object/serialization.rb +6 -0
  42. data/lib/cassandra_object/tasks/cassandra.rake +53 -0
  43. data/lib/cassandra_object/timestamps.rb +19 -0
  44. data/lib/cassandra_object/type.rb +16 -0
  45. data/lib/cassandra_object/types.rb +8 -0
  46. data/lib/cassandra_object/types/array_type.rb +76 -0
  47. data/lib/cassandra_object/types/base_type.rb +26 -0
  48. data/lib/cassandra_object/types/boolean_type.rb +20 -0
  49. data/lib/cassandra_object/types/date_type.rb +17 -0
  50. data/lib/cassandra_object/types/float_type.rb +16 -0
  51. data/lib/cassandra_object/types/integer_type.rb +16 -0
  52. data/lib/cassandra_object/types/json_type.rb +52 -0
  53. data/lib/cassandra_object/types/string_type.rb +15 -0
  54. data/lib/cassandra_object/types/time_type.rb +16 -0
  55. data/lib/cassandra_object/validations.rb +44 -0
  56. data/lib/cassandra_object_rails.rb +64 -0
  57. data/test/support/connect.rb +17 -0
  58. data/test/support/issue.rb +5 -0
  59. data/test/support/teardown.rb +24 -0
  60. data/test/test_helper.rb +34 -0
  61. data/test/unit/active_model_test.rb +18 -0
  62. data/test/unit/attribute_methods/definition_test.rb +13 -0
  63. data/test/unit/attribute_methods/dirty_test.rb +71 -0
  64. data/test/unit/attribute_methods/primary_key_test.rb +26 -0
  65. data/test/unit/attribute_methods/typecasting_test.rb +112 -0
  66. data/test/unit/attribute_methods_test.rb +39 -0
  67. data/test/unit/base_test.rb +20 -0
  68. data/test/unit/belongs_to/reflection_test.rb +12 -0
  69. data/test/unit/belongs_to_test.rb +62 -0
  70. data/test/unit/callbacks_test.rb +46 -0
  71. data/test/unit/config_test.rb +23 -0
  72. data/test/unit/connection_test.rb +10 -0
  73. data/test/unit/consistency_test.rb +13 -0
  74. data/test/unit/core_test.rb +55 -0
  75. data/test/unit/identity_test.rb +26 -0
  76. data/test/unit/inspect_test.rb +26 -0
  77. data/test/unit/log_subscriber_test.rb +22 -0
  78. data/test/unit/persistence_test.rb +187 -0
  79. data/test/unit/savepoints_test.rb +35 -0
  80. data/test/unit/schema/tasks_test.rb +29 -0
  81. data/test/unit/schema_test.rb +47 -0
  82. data/test/unit/scope/batches_test.rb +30 -0
  83. data/test/unit/scope/finder_methods_test.rb +51 -0
  84. data/test/unit/scope/query_methods_test.rb +26 -0
  85. data/test/unit/scoping_test.rb +7 -0
  86. data/test/unit/timestamps_test.rb +27 -0
  87. data/test/unit/types/array_type_test.rb +71 -0
  88. data/test/unit/types/base_type_test.rb +24 -0
  89. data/test/unit/types/boolean_type_test.rb +24 -0
  90. data/test/unit/types/date_type_test.rb +11 -0
  91. data/test/unit/types/float_type_test.rb +17 -0
  92. data/test/unit/types/integer_type_test.rb +19 -0
  93. data/test/unit/types/json_type_test.rb +77 -0
  94. data/test/unit/types/string_type_test.rb +32 -0
  95. data/test/unit/types/time_type_test.rb +14 -0
  96. data/test/unit/validations_test.rb +27 -0
  97. metadata +208 -0
@@ -0,0 +1,69 @@
1
+ require 'set'
2
+
3
+ require 'cassandra_object/log_subscriber'
4
+ require 'cassandra_object/types'
5
+
6
+ module CassandraObject
7
+ class Base
8
+ class << self
9
+ def column_family=(column_family)
10
+ @column_family = column_family
11
+ end
12
+
13
+ def column_family
14
+ @column_family ||= base_class.name.pluralize
15
+ end
16
+
17
+ def base_class
18
+ class_of_active_record_descendant(self)
19
+ end
20
+
21
+ def config=(config)
22
+ @@config = config.is_a?(Hash) ? CassandraObject::Config.new(config) : config
23
+ end
24
+
25
+ def config
26
+ @@config
27
+ end
28
+
29
+ private
30
+
31
+ # Returns the class descending directly from ActiveRecord::Base or an
32
+ # abstract class, if any, in the inheritance hierarchy.
33
+ def class_of_active_record_descendant(klass)
34
+ if klass == Base || klass.superclass == Base
35
+ klass
36
+ elsif klass.superclass.nil?
37
+ raise "#{name} doesn't belong in a hierarchy descending from CassandraObject"
38
+ else
39
+ class_of_active_record_descendant(klass.superclass)
40
+ end
41
+ end
42
+ end
43
+
44
+ extend ActiveModel::Naming
45
+ include ActiveModel::Conversion
46
+ extend ActiveSupport::DescendantsTracker
47
+
48
+ include Connection
49
+ include Consistency
50
+ include Identity
51
+ include Inspect
52
+ include Persistence
53
+ include AttributeMethods
54
+ include Validations
55
+ include AttributeMethods::Dirty
56
+ include AttributeMethods::PrimaryKey
57
+ include AttributeMethods::Typecasting
58
+ include BelongsTo
59
+ include Callbacks
60
+ include Timestamps
61
+ include Savepoints
62
+ include Scoping
63
+ include Core
64
+
65
+ include Serialization
66
+ end
67
+ end
68
+
69
+ ActiveSupport.run_load_hooks(:cassandra_object, CassandraObject::Base)
@@ -0,0 +1,63 @@
1
+ module CassandraObject
2
+ module BelongsTo
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :belongs_to_reflections
7
+ self.belongs_to_reflections = {}
8
+ end
9
+
10
+ module ClassMethods
11
+ # === Options
12
+ # [:class_name]
13
+ # Use if the class cannot be inferred from the association
14
+ # [:polymorphic]
15
+ # Specify if the association is polymorphic
16
+ # Example:
17
+ # class Driver < CassandraObject::Base
18
+ # end
19
+ # class Truck < CassandraObject::Base
20
+ # end
21
+ def belongs_to(name, options = {})
22
+ CassandraObject::BelongsTo::Builder.build(self, name, options)
23
+ end
24
+
25
+ def generated_belongs_to_methods
26
+ @generated_belongs_to_methods ||= begin
27
+ mod = const_set(:GeneratedBelongsToMethods, Module.new)
28
+ include mod
29
+ mod
30
+ end
31
+ end
32
+ end
33
+
34
+ # Returns the belongs_to instance for the given name, instantiating it if it doesn't already exist
35
+ def belongs_to_association(name)
36
+ association = belongs_to_instance_get(name)
37
+
38
+ if association.nil?
39
+ association = CassandraObject::BelongsTo::Association.new(self, belongs_to_reflections[name])
40
+ belongs_to_instance_set(name, association)
41
+ end
42
+
43
+ association
44
+ end
45
+
46
+ private
47
+ def clear_belongs_to_cache
48
+ belongs_to_cache.clear if persisted?
49
+ end
50
+
51
+ def belongs_to_cache
52
+ @belongs_to_cache ||= {}
53
+ end
54
+
55
+ def belongs_to_instance_get(name)
56
+ belongs_to_cache[name.to_sym]
57
+ end
58
+
59
+ def belongs_to_instance_set(name, association)
60
+ belongs_to_cache[name.to_sym] = association
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,48 @@
1
+ module CassandraObject
2
+ module BelongsTo
3
+ class Association
4
+ attr_reader :owner, :reflection
5
+ attr_accessor :record_variable
6
+ delegate :options, to: :reflection
7
+
8
+ def initialize(owner, reflection)
9
+ @owner = owner
10
+ @reflection = reflection
11
+ end
12
+
13
+ def reader
14
+ unless loaded?
15
+ if record_id = owner.send(reflection.foreign_key).presence
16
+ self.record_variable = association_class.find_by_id(record_id)
17
+ else
18
+ self.record_variable = nil
19
+ end
20
+ @loaded = true
21
+ end
22
+
23
+ record_variable
24
+ end
25
+
26
+ def writer(record)
27
+ self.record_variable = record
28
+ @loaded = true
29
+ owner.send("#{reflection.foreign_key}=", record.try(:id))
30
+ if reflection.polymorphic?
31
+ owner.send("#{reflection.polymorphic_column}=", record.class.name)
32
+ end
33
+ end
34
+
35
+ def association_class
36
+ association_class_name.constantize
37
+ end
38
+
39
+ def association_class_name
40
+ reflection.polymorphic? ? owner.send(reflection.polymorphic_column) : reflection.class_name
41
+ end
42
+
43
+ def loaded?
44
+ @loaded
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ module CassandraObject
2
+ module BelongsTo
3
+ class Builder
4
+ def self.build(model, name, options)
5
+ new(model, name, options).build
6
+ end
7
+
8
+ attr_reader :model, :name, :options
9
+ def initialize(model, name, options)
10
+ @model, @name, @options = model, name, options
11
+ end
12
+
13
+ def build
14
+ define_writer
15
+ define_reader
16
+
17
+ reflection = CassandraObject::BelongsTo::Reflection.new(model, name, options)
18
+ model.belongs_to_reflections = model.belongs_to_reflections.merge(name => reflection)
19
+ end
20
+
21
+ def mixin
22
+ model.generated_belongs_to_methods
23
+ end
24
+
25
+ def define_writer
26
+ name = self.name
27
+ mixin.redefine_method("#{name}=") do |records|
28
+ belongs_to_association(name).writer(records)
29
+ end
30
+ end
31
+
32
+ def define_reader
33
+ name = self.name
34
+ mixin.redefine_method(name) do
35
+ belongs_to_association(name).reader
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -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 #: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,15 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+
3
+ module CassandraObject
4
+ class Config
5
+ attr_accessor :servers, :keyspace, :thrift_options, :keyspace_options
6
+
7
+ def initialize(options)
8
+ options = options.symbolize_keys
9
+ self.servers = Array.wrap(options[:servers] || "127.0.0.1:9160")
10
+ self.keyspace = options[:keyspace]
11
+ self.thrift_options = (options[:thrift] || {}).symbolize_keys
12
+ self.keyspace_options = (options[:keyspace_options] || {}).symbolize_keys
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ module CassandraCQL
2
+ class Statement
3
+ def self.sanitize(statement, bind_vars=[])
4
+ return statement if bind_vars.empty?
5
+
6
+ bind_vars = bind_vars.dup
7
+ expected_bind_vars = statement.count("?")
8
+
9
+ raise Error::InvalidBindVariable, "Wrong number of bound variables (statement expected #{expected_bind_vars}, was #{bind_vars.size})" if expected_bind_vars != bind_vars.size
10
+
11
+ statement.gsub(/\?/) do
12
+ quote(cast_to_cql(bind_vars.shift))
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ module CassandraObject
19
+ module Connection
20
+ extend ActiveSupport::Concern
21
+
22
+ module ClassMethods
23
+ def cql
24
+ @@cql ||= CassandraCQL::Database.new(config.servers, {keyspace: config.keyspace}, config.thrift_options)
25
+ end
26
+
27
+ def execute_cql(cql_string, *bind_vars)
28
+ statement = CassandraCQL::Statement.sanitize(cql_string, bind_vars).force_encoding(Encoding::UTF_8)
29
+
30
+ ActiveSupport::Notifications.instrument("cql.cassandra_object", cql: statement) do
31
+ cql.execute statement
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ module CassandraObject
2
+ module Consistency
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def with_consistency(consistency)
7
+ previous, self.default_consistency = default_consistency, consistency
8
+ yield
9
+ ensure
10
+ self.default_consistency = previous
11
+ end
12
+ end
13
+
14
+ included do
15
+ class_attribute :default_consistency
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,59 @@
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 do |attr, attribute_definition|
11
+ unless attribute_exists?(attr)
12
+ @attributes[attr.to_s] = self.class.typecast_attribute(self, attr, nil)
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 hash
35
+ id.hash
36
+ end
37
+
38
+ module ClassMethods
39
+ def inspect
40
+ if self == Base
41
+ super
42
+ else
43
+ attr_list = @attributes.map do |col, definition| "#{col}: #{definition.type}" end * ', '
44
+ "#{super}(#{attr_list.truncate(140 * 1.7337)})"
45
+ end
46
+ end
47
+ end
48
+
49
+ def ==(comparison_object)
50
+ comparison_object.equal?(self) ||
51
+ (comparison_object.instance_of?(self.class) &&
52
+ comparison_object.id == id)
53
+ end
54
+
55
+ def eql?(comparison_object)
56
+ self == (comparison_object)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ module CassandraObject
2
+
3
+ class CasssandraObjectError < StandardError; end
4
+ class RecordNotSaved < CasssandraObjectError; end
5
+ class RecordNotFound < CasssandraObjectError; end
6
+ end
@@ -0,0 +1,24 @@
1
+ module CassandraObject
2
+ module Identity
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :key_generator
7
+
8
+ key do
9
+ SimpleUUID::UUID.new.to_guid.tr('-','')
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ # Define a key generator. Default is UUID.
15
+ def key(&block)
16
+ self.key_generator = block
17
+ end
18
+
19
+ def _generate_key(object)
20
+ object.instance_eval(&key_generator)
21
+ end
22
+ end
23
+ end
24
+ end