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