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