sessionm-cassandra_object 2.2.6
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.
- data/.gitignore +2 -0
- data/CHANGELOG +3 -0
- data/Gemfile +2 -0
- data/LICENSE +13 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +12 -0
- data/Rakefile +15 -0
- data/lib/cassandra_object/associations/one_to_many.rb +146 -0
- data/lib/cassandra_object/associations/one_to_one.rb +85 -0
- data/lib/cassandra_object/associations.rb +50 -0
- data/lib/cassandra_object/attributes.rb +97 -0
- data/lib/cassandra_object/base.rb +97 -0
- data/lib/cassandra_object/batches.rb +31 -0
- data/lib/cassandra_object/callbacks.rb +27 -0
- data/lib/cassandra_object/collection.rb +8 -0
- data/lib/cassandra_object/connection.rb +29 -0
- data/lib/cassandra_object/consistency.rb +31 -0
- data/lib/cassandra_object/cursor.rb +90 -0
- data/lib/cassandra_object/dirty.rb +32 -0
- data/lib/cassandra_object/errors.rb +10 -0
- data/lib/cassandra_object/finder_methods.rb +72 -0
- data/lib/cassandra_object/generators/migration_generator.rb +31 -0
- data/lib/cassandra_object/generators/templates/migration.rb.erb +11 -0
- data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
- data/lib/cassandra_object/identity/custom_key_factory.rb +50 -0
- data/lib/cassandra_object/identity/hashed_natural_key_factory.rb +10 -0
- data/lib/cassandra_object/identity/key.rb +20 -0
- data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
- data/lib/cassandra_object/identity/uuid_key_factory.rb +39 -0
- data/lib/cassandra_object/identity.rb +52 -0
- data/lib/cassandra_object/log_subscriber.rb +37 -0
- data/lib/cassandra_object/migrations/migration.rb +15 -0
- data/lib/cassandra_object/migrations.rb +66 -0
- data/lib/cassandra_object/mocking.rb +15 -0
- data/lib/cassandra_object/persistence.rb +138 -0
- data/lib/cassandra_object/railtie.rb +11 -0
- data/lib/cassandra_object/schema/migration.rb +106 -0
- data/lib/cassandra_object/schema/migration_proxy.rb +25 -0
- data/lib/cassandra_object/schema/migrator.rb +213 -0
- data/lib/cassandra_object/schema.rb +37 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/tasks/column_family.rb +90 -0
- data/lib/cassandra_object/tasks/keyspace.rb +89 -0
- data/lib/cassandra_object/tasks/ks.rake +121 -0
- data/lib/cassandra_object/timestamps.rb +19 -0
- data/lib/cassandra_object/type.rb +19 -0
- data/lib/cassandra_object/types/array_type.rb +16 -0
- data/lib/cassandra_object/types/boolean_type.rb +23 -0
- data/lib/cassandra_object/types/date_type.rb +20 -0
- data/lib/cassandra_object/types/float_type.rb +19 -0
- data/lib/cassandra_object/types/hash_type.rb +16 -0
- data/lib/cassandra_object/types/integer_type.rb +19 -0
- data/lib/cassandra_object/types/set_type.rb +22 -0
- data/lib/cassandra_object/types/string_type.rb +16 -0
- data/lib/cassandra_object/types/time_type.rb +27 -0
- data/lib/cassandra_object/types/time_with_zone_type.rb +18 -0
- data/lib/cassandra_object/types/utf8_string_type.rb +18 -0
- data/lib/cassandra_object/types.rb +11 -0
- data/lib/cassandra_object/validations.rb +46 -0
- data/lib/cassandra_object.rb +49 -0
- data/sessionm-cassandra_object.gemspec +26 -0
- data/test/active_model_test.rb +9 -0
- data/test/base_test.rb +28 -0
- data/test/batches_test.rb +30 -0
- data/test/connection_test.rb +28 -0
- data/test/consistency_test.rb +20 -0
- data/test/finder_methods_test.rb +49 -0
- data/test/identity_test.rb +30 -0
- data/test/persistence_test.rb +84 -0
- data/test/test_helper.rb +31 -0
- data/test/timestamps_test.rb +27 -0
- data/test/types/array_type_test.rb +15 -0
- data/test/types/boolean_type_test.rb +23 -0
- data/test/types/date_type_test.rb +4 -0
- data/test/types/float_type_test.rb +4 -0
- data/test/types/hash_type_test.rb +4 -0
- data/test/types/integer_type_test.rb +18 -0
- data/test/types/set_type_test.rb +17 -0
- data/test/types/string_type_test.rb +4 -0
- data/test/types/time_type_test.rb +4 -0
- data/test/types/utf8_string_type_test.rb +4 -0
- data/test/validations_test.rb +15 -0
- metadata +183 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class Cursor
|
3
|
+
include Consistency
|
4
|
+
|
5
|
+
def initialize(target_class, column_family, key, super_column, options={})
|
6
|
+
@target_class = target_class
|
7
|
+
@column_family = column_family
|
8
|
+
@key = key.to_s
|
9
|
+
@super_column = super_column
|
10
|
+
@options = options
|
11
|
+
@validators = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def find(number_to_find)
|
15
|
+
limit = number_to_find
|
16
|
+
objects = CassandraObject::Collection.new
|
17
|
+
out_of_keys = false
|
18
|
+
|
19
|
+
if start_with = @options[:start_after]
|
20
|
+
limit += 1
|
21
|
+
else
|
22
|
+
start_with = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
while objects.size < number_to_find && !out_of_keys
|
26
|
+
index_results = connection.get(@column_family, @key, @super_column,
|
27
|
+
count: limit,
|
28
|
+
start: start_with,
|
29
|
+
reversed: @options[:reversed],
|
30
|
+
consistency: target_class.thrift_read_consistency)
|
31
|
+
|
32
|
+
out_of_keys = index_results.size < limit
|
33
|
+
|
34
|
+
if !start_with.blank?
|
35
|
+
index_results.delete(start_with)
|
36
|
+
end
|
37
|
+
|
38
|
+
keys = index_results.keys
|
39
|
+
values = index_results.values
|
40
|
+
|
41
|
+
missing_keys = []
|
42
|
+
|
43
|
+
results = values.empty? ? {} : @target_class.multi_get(values)
|
44
|
+
results.each do |(key, result)|
|
45
|
+
if result.nil?
|
46
|
+
missing_keys << key
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
unless missing_keys.empty?
|
51
|
+
@target_class.multi_get(missing_keys, :quorum=>true).each do |(key, result)|
|
52
|
+
index_key = index_results.index(key)
|
53
|
+
if result.nil?
|
54
|
+
remove(index_key)
|
55
|
+
results.delete(key)
|
56
|
+
else
|
57
|
+
results[key] = result
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
results.values.each do |o|
|
63
|
+
if @validators.all? {|v| v.call(o) }
|
64
|
+
objects << o
|
65
|
+
else
|
66
|
+
remove(index_results.index(o.key))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
start_with = objects.last_column_name = keys.last
|
71
|
+
limit = (number_to_find - results.size) + 1
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
return objects
|
76
|
+
end
|
77
|
+
|
78
|
+
def connection
|
79
|
+
@target_class.connection
|
80
|
+
end
|
81
|
+
|
82
|
+
def remove(index_key)
|
83
|
+
connection.remove(@column_family, @key, @super_column, index_key, consistency: target_class.thrift_write_consistency)
|
84
|
+
end
|
85
|
+
|
86
|
+
def validator(&validator)
|
87
|
+
@validators << validator
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Dirty
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::Dirty
|
5
|
+
|
6
|
+
# Attempts to +save+ the record and clears changed attributes if successful.
|
7
|
+
def save(*) #:nodoc:
|
8
|
+
if status = super
|
9
|
+
@previously_changed = changes
|
10
|
+
@changed_attributes.clear
|
11
|
+
end
|
12
|
+
status
|
13
|
+
end
|
14
|
+
|
15
|
+
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
|
16
|
+
def save!(*) #:nodoc:
|
17
|
+
super.tap do
|
18
|
+
@previously_changed = changes
|
19
|
+
@changed_attributes.clear
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_attribute(name, value)
|
24
|
+
name = name.to_s
|
25
|
+
unless attribute_changed?(name)
|
26
|
+
old = read_attribute(name)
|
27
|
+
changed_attributes[name] = old if old != value
|
28
|
+
end
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module FinderMethods
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
module ClassMethods
|
5
|
+
def find(key)
|
6
|
+
if parse_key(key) && attributes = connection.get(column_family, key)
|
7
|
+
instantiate(key, attributes)
|
8
|
+
else
|
9
|
+
raise CassandraObject::RecordNotFound
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_by_id(key)
|
14
|
+
find(key)
|
15
|
+
rescue CassandraObject::RecordNotFound
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def all(options = {})
|
20
|
+
limit = options[:limit] || 100
|
21
|
+
results = ActiveSupport::Notifications.instrument("get_range.cassandra_object", column_family: column_family, key_count: limit) do
|
22
|
+
connection.get_range(column_family, key_count: limit, consistency: thrift_read_consistency)
|
23
|
+
end
|
24
|
+
|
25
|
+
results.map do |k, v|
|
26
|
+
v.empty? ? nil : instantiate(k, v)
|
27
|
+
end.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
def first(options = {})
|
31
|
+
all(options.merge(:limit => 1)).first
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_with_ids(*ids)
|
35
|
+
expects_array = ids.first.kind_of?(Array)
|
36
|
+
return ids.first if expects_array && ids.first.empty?
|
37
|
+
|
38
|
+
ids = ids.dup
|
39
|
+
ids.flatten!
|
40
|
+
ids.compact!
|
41
|
+
ids.collect!(&:to_s)
|
42
|
+
ids.uniq!
|
43
|
+
|
44
|
+
#raise RecordNotFound, "Couldn't find #{record_klass.name} without an ID" if ids.empty?
|
45
|
+
|
46
|
+
results = multi_get(ids).values.compact
|
47
|
+
|
48
|
+
results.size <= 1 && !expects_array ? results.first : results
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def multi_get(keys, options={})
|
53
|
+
attribute_results = ActiveSupport::Notifications.instrument("multi_get.cassandra_object", column_family: column_family, keys: keys) do
|
54
|
+
connection.multi_get(column_family, keys.map(&:to_s), consistency: thrift_read_consistency)
|
55
|
+
end
|
56
|
+
|
57
|
+
attribute_results.inject({}) do |memo, (key, attributes)|
|
58
|
+
if attributes.empty?
|
59
|
+
memo[key] = nil
|
60
|
+
else
|
61
|
+
memo[parse_key(key)] = instantiate(key, attributes)
|
62
|
+
end
|
63
|
+
memo
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def get(key, options={})
|
68
|
+
multi_get([key], options).values.first
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/named_base'
|
3
|
+
|
4
|
+
module CassandraObject
|
5
|
+
module Generators
|
6
|
+
class MigrationGenerator < Rails::Generators::NamedBase
|
7
|
+
|
8
|
+
source_root File.expand_path("../templates", __FILE__)
|
9
|
+
|
10
|
+
def self.banner
|
11
|
+
"rails g cassandra_object:migration NAME"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.desc(description = nil)
|
15
|
+
<<EOF
|
16
|
+
Description:
|
17
|
+
Create an empty Cassandra migration file in 'ks/migrate'. Very similar to Rails database migrations.
|
18
|
+
|
19
|
+
Example:
|
20
|
+
`rails g cassandra_object:migration CreateFooColumnFamily`
|
21
|
+
EOF
|
22
|
+
end
|
23
|
+
|
24
|
+
def create
|
25
|
+
timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
26
|
+
template 'migration.rb.erb', "ks/migrate/#{timestamp}_#{file_name.underscore}.rb"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Identity
|
3
|
+
# Key factories need to support 3 operations
|
4
|
+
class AbstractKeyFactory
|
5
|
+
# Next key takes an object and returns the key object it should use.
|
6
|
+
# object will be ignored with synthetic keys but could be useful with natural ones
|
7
|
+
#
|
8
|
+
# @param [CassandraObject::Base] the object that needs a new key
|
9
|
+
# @return [CassandraObject::Identity::Key] the key
|
10
|
+
#
|
11
|
+
def next_key(object)
|
12
|
+
raise NotImplementedError, "#{self.class.name}#next_key isn't implemented."
|
13
|
+
end
|
14
|
+
|
15
|
+
# Parse should create a new key object from the 'to_param' format
|
16
|
+
#
|
17
|
+
# @param [String] the result of calling key.to_param
|
18
|
+
# @return [CassandraObject::Identity::Key] the parsed key
|
19
|
+
#
|
20
|
+
def parse(string)
|
21
|
+
raise NotImplementedError, "#{self.class.name}#parse isn't implemented."
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# create should create a new key object from the cassandra format.
|
26
|
+
#
|
27
|
+
# @param [String] the result of calling key.to_s
|
28
|
+
# @return [CassandraObject::Identity::Key] the key
|
29
|
+
#
|
30
|
+
def create(string)
|
31
|
+
raise NotImplementedError, "#{self.class.name}#create isn't implemented."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Identity
|
3
|
+
class CustomKeyFactory < AbstractKeyFactory
|
4
|
+
class CustomKey
|
5
|
+
include Key
|
6
|
+
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
def initialize(value)
|
10
|
+
@value = value
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
value
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_param
|
18
|
+
value
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
other.is_a?(CustomKey) && other.value == value
|
23
|
+
end
|
24
|
+
|
25
|
+
def eql?(other)
|
26
|
+
other == self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :method
|
31
|
+
|
32
|
+
def initialize(options)
|
33
|
+
@method = options[:method]
|
34
|
+
end
|
35
|
+
|
36
|
+
def next_key(object)
|
37
|
+
CustomKey.new(object.send(@method))
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse(paramized_key)
|
41
|
+
CustomKey.new(paramized_key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create(paramized_key)
|
45
|
+
CustomKey.new(paramized_key)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
module CassandraObject
|
3
|
+
module Identity
|
4
|
+
class HashedNaturalKeyFactory < NaturalKeyFactory
|
5
|
+
def next_key(object)
|
6
|
+
NaturalKey.new(Digest::SHA1.hexdigest(attributes.map { |a| object.attributes[a.to_s] }.join(separator)))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Identity
|
3
|
+
# An "interface" that keys need to implement
|
4
|
+
#
|
5
|
+
# You don't have to include this. But, there's no reason I can think of not to.
|
6
|
+
#
|
7
|
+
module Key
|
8
|
+
# to_param should return a nice-readable representation of the key suitable to chuck into URLs
|
9
|
+
#
|
10
|
+
# @return [String] a nice readable representation of the key suitable for URLs
|
11
|
+
def to_param; end
|
12
|
+
|
13
|
+
# to_s should return the bytes which will be written to cassandra both as keys and values for associations.
|
14
|
+
#
|
15
|
+
# @return [String] the bytes which will be written to cassandra as keys
|
16
|
+
def to_s; end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Identity
|
3
|
+
class NaturalKeyFactory < AbstractKeyFactory
|
4
|
+
class NaturalKey
|
5
|
+
include Key
|
6
|
+
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
def initialize(value)
|
10
|
+
@value = value
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
value
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_param
|
18
|
+
value
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
other.is_a?(NaturalKey) && other.value == value
|
23
|
+
end
|
24
|
+
|
25
|
+
def eql?(other)
|
26
|
+
other == self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :attributes, :separator
|
31
|
+
|
32
|
+
def initialize(options)
|
33
|
+
@attributes = [*options[:attributes]]
|
34
|
+
@separator = options[:separator] || "-"
|
35
|
+
end
|
36
|
+
|
37
|
+
def next_key(object)
|
38
|
+
NaturalKey.new(attributes.map { |a| object.attributes[a.to_s] }.join(separator))
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(paramized_key)
|
42
|
+
NaturalKey.new(paramized_key)
|
43
|
+
end
|
44
|
+
|
45
|
+
def create(paramized_key)
|
46
|
+
NaturalKey.new(paramized_key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Identity
|
3
|
+
# Key factories need to support 3 operations
|
4
|
+
class UUIDKeyFactory < AbstractKeyFactory
|
5
|
+
class UUID < SimpleUUID::UUID
|
6
|
+
include Key
|
7
|
+
|
8
|
+
def to_param
|
9
|
+
to_guid
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
# FIXME - this should probably write the raw bytes
|
14
|
+
# but it's very hard to debug without this for now.
|
15
|
+
to_guid
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Next key takes an object and returns the key object it should use.
|
20
|
+
# object will be ignored with synthetic keys but could be useful with natural ones
|
21
|
+
def next_key(object)
|
22
|
+
UUID.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parse should create a new key object from the 'to_param' format
|
26
|
+
def parse(string)
|
27
|
+
UUID.new(string)
|
28
|
+
rescue
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# create should create a new key object from the cassandra format.
|
33
|
+
def create(string)
|
34
|
+
UUID.new(string)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Identity
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
autoload :Key
|
7
|
+
autoload :AbstractKeyFactory
|
8
|
+
autoload :UUIDKeyFactory
|
9
|
+
autoload :NaturalKeyFactory
|
10
|
+
autoload :HashedNaturalKeyFactory
|
11
|
+
autoload :CustomKeyFactory
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# Indicate what kind of key the model will have: uuid or natural
|
15
|
+
#
|
16
|
+
# @param [:uuid, :natural] the type of key
|
17
|
+
# @param the options you want to pass along to the key factory (like :attributes => :name, for a natural key).
|
18
|
+
#
|
19
|
+
def key(name_or_factory = :uuid, *options)
|
20
|
+
@key_factory = case name_or_factory
|
21
|
+
when :uuid
|
22
|
+
UUIDKeyFactory.new
|
23
|
+
when :natural
|
24
|
+
NaturalKeyFactory.new(*options)
|
25
|
+
when :custom
|
26
|
+
CustomKeyFactory.new(*options)
|
27
|
+
else
|
28
|
+
name_or_factory
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def next_key(object = nil)
|
33
|
+
@key_factory.next_key(object).tap do |key|
|
34
|
+
raise "Keys may not be nil" if key.nil?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def parse_key(string)
|
39
|
+
@key_factory.parse(string)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def id
|
44
|
+
key.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def id=(key)
|
48
|
+
self.key = self.class.parse_key(key)
|
49
|
+
id
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def multi_get(event)
|
4
|
+
name = '%s multi_get (%.1fms)' % [event.payload[:column_family], event.duration]
|
5
|
+
|
6
|
+
debug " #{name} (#{event.payload[:keys].size}) #{event.payload[:keys].join(" ")}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove(event)
|
10
|
+
name = '%s remove (%.1fms)' % [event.payload[:column_family], event.duration]
|
11
|
+
|
12
|
+
message = " #{name} #{event.payload[:key]}"
|
13
|
+
message << " #{Array(event.payload[:attributes]).inspect}" if event.payload[:attributes]
|
14
|
+
|
15
|
+
debug message
|
16
|
+
end
|
17
|
+
|
18
|
+
def truncate(event)
|
19
|
+
name = '%s truncate (%.1fms)' % [event.payload[:column_family], event.duration]
|
20
|
+
|
21
|
+
debug " #{name} #{event.payload[:column_family]}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def insert(event)
|
25
|
+
name = '%s insert (%.1fms)' % [event.payload[:column_family], event.duration]
|
26
|
+
|
27
|
+
debug " #{name} #{event.payload[:key]} #{event.payload[:attributes].inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_range(event)
|
31
|
+
name = '%s get_range (%.1fms)' % [event.payload[:column_family], event.duration]
|
32
|
+
|
33
|
+
debug " #{name} (#{event.payload[:count]}) '#{event.payload[:start]}' => '#{event.payload[:finish]}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
CassandraObject::LogSubscriber.attach_to :cassandra_object
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Migrations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_inheritable_array :migrations
|
8
|
+
class_inheritable_accessor :current_schema_version
|
9
|
+
self.current_schema_version = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
autoload :Migration
|
13
|
+
|
14
|
+
class MigrationNotFoundError < StandardError
|
15
|
+
def initialize(record_version, migrations)
|
16
|
+
super("Cannot migrate a record from #{record_version.inspect}. Migrations exist for #{migrations.map(&:version)}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceMethods
|
21
|
+
def schema_version
|
22
|
+
Integer(@schema_version || self.class.current_schema_version)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def migrate(version, &blk)
|
28
|
+
write_inheritable_array(:migrations, [Migration.new(version, blk)])
|
29
|
+
|
30
|
+
if version > self.current_schema_version
|
31
|
+
self.current_schema_version = version
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def instantiate(key, attributes)
|
36
|
+
version = attributes.delete('schema_version')
|
37
|
+
original_attributes = attributes.dup
|
38
|
+
if version == current_schema_version
|
39
|
+
return super(key, attributes)
|
40
|
+
end
|
41
|
+
|
42
|
+
versions_to_migrate = ((version.to_i + 1)..current_schema_version)
|
43
|
+
|
44
|
+
migrations_to_run = versions_to_migrate.map do |v|
|
45
|
+
migrations.find {|m| m.version == v}
|
46
|
+
end
|
47
|
+
|
48
|
+
if migrations_to_run.any?(&:nil?)
|
49
|
+
raise MigrationNotFoundError.new(version, migrations)
|
50
|
+
end
|
51
|
+
|
52
|
+
migrations_to_run.inject(attributes) do |attrs, migration|
|
53
|
+
migration.run(attrs)
|
54
|
+
@schema_version = migration.version.to_s
|
55
|
+
attrs
|
56
|
+
end
|
57
|
+
|
58
|
+
super(key, attributes).tap do |record|
|
59
|
+
original_attributes.diff(attributes).keys.each do |attribute|
|
60
|
+
record.attribute_will_change! attribute
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'cassandra/mock'
|
2
|
+
module CassandraObject
|
3
|
+
module Mocking
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
module ClassMethods
|
6
|
+
def use_mock!(really=true)
|
7
|
+
if really
|
8
|
+
self.connection_class = Cassandra::Mock
|
9
|
+
else
|
10
|
+
self.connection_class = Cassandra
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|