cassandra_object_rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +3 -0
- data/.travis.yml +7 -0
- data/CHANGELOG +5 -0
- data/Gemfile +8 -0
- data/LICENSE +13 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +97 -0
- data/Rakefile +12 -0
- data/cassandra_object_rails.gemspec +26 -0
- data/lib/cassandra_object/attribute_methods.rb +87 -0
- data/lib/cassandra_object/attribute_methods/definition.rb +19 -0
- data/lib/cassandra_object/attribute_methods/dirty.rb +44 -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 +69 -0
- data/lib/cassandra_object/belongs_to.rb +63 -0
- data/lib/cassandra_object/belongs_to/association.rb +48 -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/config.rb +15 -0
- data/lib/cassandra_object/connection.rb +36 -0
- data/lib/cassandra_object/consistency.rb +18 -0
- data/lib/cassandra_object/core.rb +59 -0
- data/lib/cassandra_object/errors.rb +6 -0
- data/lib/cassandra_object/identity.rb +24 -0
- data/lib/cassandra_object/inspect.rb +25 -0
- data/lib/cassandra_object/log_subscriber.rb +29 -0
- data/lib/cassandra_object/persistence.rb +169 -0
- data/lib/cassandra_object/rails_initializer.rb +19 -0
- data/lib/cassandra_object/railtie.rb +11 -0
- data/lib/cassandra_object/savepoints.rb +79 -0
- data/lib/cassandra_object/schema.rb +78 -0
- data/lib/cassandra_object/schema/tasks.rb +48 -0
- data/lib/cassandra_object/scope.rb +48 -0
- data/lib/cassandra_object/scope/batches.rb +32 -0
- data/lib/cassandra_object/scope/finder_methods.rb +47 -0
- data/lib/cassandra_object/scope/query_methods.rb +111 -0
- data/lib/cassandra_object/scoping.rb +19 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/tasks/cassandra.rake +53 -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 +76 -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 +17 -0
- data/lib/cassandra_object/types/float_type.rb +16 -0
- data/lib/cassandra_object/types/integer_type.rb +16 -0
- data/lib/cassandra_object/types/json_type.rb +52 -0
- data/lib/cassandra_object/types/string_type.rb +15 -0
- data/lib/cassandra_object/types/time_type.rb +16 -0
- data/lib/cassandra_object/validations.rb +44 -0
- data/lib/cassandra_object_rails.rb +64 -0
- data/test/support/connect.rb +17 -0
- data/test/support/issue.rb +5 -0
- data/test/support/teardown.rb +24 -0
- data/test/test_helper.rb +34 -0
- data/test/unit/active_model_test.rb +18 -0
- data/test/unit/attribute_methods/definition_test.rb +13 -0
- data/test/unit/attribute_methods/dirty_test.rb +71 -0
- data/test/unit/attribute_methods/primary_key_test.rb +26 -0
- data/test/unit/attribute_methods/typecasting_test.rb +112 -0
- data/test/unit/attribute_methods_test.rb +39 -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 +62 -0
- data/test/unit/callbacks_test.rb +46 -0
- data/test/unit/config_test.rb +23 -0
- data/test/unit/connection_test.rb +10 -0
- data/test/unit/consistency_test.rb +13 -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 +22 -0
- data/test/unit/persistence_test.rb +187 -0
- data/test/unit/savepoints_test.rb +35 -0
- data/test/unit/schema/tasks_test.rb +29 -0
- data/test/unit/schema_test.rb +47 -0
- data/test/unit/scope/batches_test.rb +30 -0
- data/test/unit/scope/finder_methods_test.rb +51 -0
- data/test/unit/scope/query_methods_test.rb +26 -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 +71 -0
- data/test/unit/types/base_type_test.rb +24 -0
- data/test/unit/types/boolean_type_test.rb +24 -0
- data/test/unit/types/date_type_test.rb +11 -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 +77 -0
- data/test/unit/types/string_type_test.rb +32 -0
- data/test/unit/types/time_type_test.rb +14 -0
- data/test/unit/validations_test.rb +27 -0
- 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,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
|