cassandra_object_rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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