gotime-cassandra_object 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +42 -0
- data/LICENSE +13 -0
- data/README.markdown +79 -0
- data/Rakefile +74 -0
- data/TODO +2 -0
- data/VERSION +1 -0
- data/gotime-cassandra_object.gemspec +134 -0
- data/lib/cassandra_object.rb +13 -0
- data/lib/cassandra_object/associations.rb +35 -0
- data/lib/cassandra_object/associations/one_to_many.rb +136 -0
- data/lib/cassandra_object/associations/one_to_one.rb +77 -0
- data/lib/cassandra_object/attributes.rb +93 -0
- data/lib/cassandra_object/base.rb +97 -0
- data/lib/cassandra_object/callbacks.rb +10 -0
- data/lib/cassandra_object/collection.rb +8 -0
- data/lib/cassandra_object/cursor.rb +86 -0
- data/lib/cassandra_object/dirty.rb +27 -0
- data/lib/cassandra_object/identity.rb +61 -0
- data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -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 +37 -0
- data/lib/cassandra_object/indexes.rb +129 -0
- data/lib/cassandra_object/log_subscriber.rb +17 -0
- data/lib/cassandra_object/migrations.rb +72 -0
- data/lib/cassandra_object/mocking.rb +15 -0
- data/lib/cassandra_object/persistence.rb +195 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/type_registration.rb +7 -0
- data/lib/cassandra_object/types.rb +128 -0
- data/lib/cassandra_object/validation.rb +49 -0
- data/test/basic_scenarios_test.rb +243 -0
- data/test/callbacks_test.rb +19 -0
- data/test/config/cassandra.in.sh +53 -0
- data/test/config/log4j.properties +38 -0
- data/test/config/storage-conf.xml +221 -0
- data/test/connection.rb +25 -0
- data/test/cursor_test.rb +66 -0
- data/test/dirty_test.rb +34 -0
- data/test/fixture_models.rb +90 -0
- data/test/identity/natural_key_factory_test.rb +94 -0
- data/test/index_test.rb +69 -0
- data/test/legacy/test_helper.rb +18 -0
- data/test/migration_test.rb +21 -0
- data/test/one_to_many_associations_test.rb +163 -0
- data/test/test_case.rb +28 -0
- data/test/test_helper.rb +16 -0
- data/test/time_test.rb +32 -0
- data/test/types_test.rb +252 -0
- data/test/validation_test.rb +25 -0
- data/test/z_mock_test.rb +36 -0
- metadata +243 -0
@@ -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,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,37 @@
|
|
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
|
+
end
|
29
|
+
|
30
|
+
# create should create a new key object from the cassandra format.
|
31
|
+
def create(string)
|
32
|
+
UUID.new(string)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Indexes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_inheritable_accessor :indexes
|
7
|
+
end
|
8
|
+
|
9
|
+
class UniqueIndex
|
10
|
+
def initialize(attribute_name, model_class, options)
|
11
|
+
@attribute_name = attribute_name
|
12
|
+
@model_class = model_class
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(attribute_value)
|
16
|
+
# first find the key value
|
17
|
+
key = @model_class.connection.get(column_family, attribute_value.to_s, 'key')
|
18
|
+
# then pass to get
|
19
|
+
if key
|
20
|
+
@model_class.get(key.to_s)
|
21
|
+
else
|
22
|
+
@model_class.connection.remove(column_family, attribute_value.to_s)
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(record)
|
28
|
+
@model_class.connection.insert(column_family, record.send(@attribute_name).to_s, {'key'=>record.key.to_s})
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove(record)
|
32
|
+
@model_class.connection.remove(column_family, record.send(@attribute_name).to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def column_family
|
36
|
+
@model_class.column_family + "By" + @attribute_name.to_s.camelize
|
37
|
+
end
|
38
|
+
|
39
|
+
def column_family_configuration
|
40
|
+
{:Name=>column_family, :CompareWith=>"UTF8Type"}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Index
|
45
|
+
def initialize(attribute_name, model_class, options)
|
46
|
+
@attribute_name = attribute_name
|
47
|
+
@model_class = model_class
|
48
|
+
@reversed = options[:reversed]
|
49
|
+
end
|
50
|
+
|
51
|
+
def find(attribute_value, options = {})
|
52
|
+
cursor = CassandraObject::Cursor.new(@model_class, column_family, attribute_value.to_s, @attribute_name.to_s, :start_after=>options[:start_after], :reversed=>@reversed)
|
53
|
+
cursor.validator do |object|
|
54
|
+
object.send(@attribute_name) == attribute_value
|
55
|
+
end
|
56
|
+
cursor.find(options[:limit] || 100)
|
57
|
+
end
|
58
|
+
|
59
|
+
def write(record)
|
60
|
+
@model_class.connection.insert(column_family, record.send(@attribute_name).to_s, {@attribute_name.to_s=>{new_key=>record.key.to_s}})
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove(record)
|
64
|
+
end
|
65
|
+
|
66
|
+
def column_family
|
67
|
+
@model_class.column_family + "By" + @attribute_name.to_s.camelize
|
68
|
+
end
|
69
|
+
|
70
|
+
def new_key
|
71
|
+
SimpleUUID::UUID.new
|
72
|
+
end
|
73
|
+
|
74
|
+
def column_family_configuration
|
75
|
+
{:Name=>column_family, :CompareWith=>"UTF8Type", :ColumnType=>"Super", :CompareSubcolumnsWith=>"TimeUUIDType"}
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
module ClassMethods
|
81
|
+
def column_family_configuration
|
82
|
+
if indexes
|
83
|
+
super + indexes.values.map(&:column_family_configuration)
|
84
|
+
else
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def index(attribute_name, options = {})
|
90
|
+
self.indexes ||= {}.with_indifferent_access
|
91
|
+
if options.delete(:unique)
|
92
|
+
self.indexes[attribute_name] = UniqueIndex.new(attribute_name, self, options)
|
93
|
+
class_eval <<-eom
|
94
|
+
def self.find_by_#{attribute_name}(value)
|
95
|
+
indexes[:#{attribute_name}].find(value)
|
96
|
+
end
|
97
|
+
|
98
|
+
after_save do |record|
|
99
|
+
self.indexes[:#{attribute_name}].write(record)
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
after_destroy do |record|
|
104
|
+
record.class.indexes[:#{attribute_name}].remove(record)
|
105
|
+
true
|
106
|
+
end
|
107
|
+
eom
|
108
|
+
else
|
109
|
+
self.indexes[attribute_name] = Index.new(attribute_name, self, options)
|
110
|
+
class_eval <<-eom
|
111
|
+
def self.find_all_by_#{attribute_name}(value, options = {})
|
112
|
+
self.indexes[:#{attribute_name}].find(value, options)
|
113
|
+
end
|
114
|
+
|
115
|
+
after_save do |record|
|
116
|
+
record.class.indexes[:#{attribute_name}].write(record)
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
after_destroy do |record|
|
121
|
+
record.class.indexes[:#{attribute_name}].remove(record)
|
122
|
+
true
|
123
|
+
end
|
124
|
+
eom
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
def multi_get(event)
|
4
|
+
name = 'CassandraObject multi_get (%.1fms)'
|
5
|
+
keys = event.payload[:keys].join(" ")
|
6
|
+
|
7
|
+
debug " #{name} (#{keys.size}) #{keys}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def remove(event)
|
11
|
+
end
|
12
|
+
|
13
|
+
def insert(event)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
CassandraObject::LogSubscriber.attach_to :cassandra_object
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Migrations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
included do
|
5
|
+
class_inheritable_array :migrations
|
6
|
+
class_inheritable_accessor :current_schema_version
|
7
|
+
self.current_schema_version = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
class Migration
|
11
|
+
attr_reader :version
|
12
|
+
def initialize(version, block)
|
13
|
+
@version = version
|
14
|
+
@block = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(attrs)
|
18
|
+
@block.call(attrs)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class MigrationNotFoundError < StandardError
|
23
|
+
def initialize(record_version, migrations)
|
24
|
+
super("Cannot migrate a record from #{record_version.inspect}. Migrations exist for #{migrations.map(&:version)}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module InstanceMethods
|
29
|
+
def schema_version
|
30
|
+
Integer(@schema_version || self.class.current_schema_version)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
def migrate(version, &blk)
|
36
|
+
write_inheritable_array(:migrations, [Migration.new(version, blk)])
|
37
|
+
|
38
|
+
if version > self.current_schema_version
|
39
|
+
self.current_schema_version = version
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def instantiate(key, attributes)
|
44
|
+
version = attributes.delete('schema_version')
|
45
|
+
original_attributes = attributes.dup
|
46
|
+
if version == current_schema_version
|
47
|
+
return super(key, attributes)
|
48
|
+
end
|
49
|
+
|
50
|
+
versions_to_migrate = ((version.to_i + 1)..current_schema_version)
|
51
|
+
|
52
|
+
migrations_to_run = versions_to_migrate.map do |v|
|
53
|
+
migrations.find {|m| m.version == v}
|
54
|
+
end
|
55
|
+
|
56
|
+
if migrations_to_run.any?(&:nil?)
|
57
|
+
raise MigrationNotFoundError.new(version, migrations)
|
58
|
+
end
|
59
|
+
|
60
|
+
migrations_to_run.inject(attributes) do |attrs, migration|
|
61
|
+
migration.run(attrs)
|
62
|
+
@schema_version = migration.version.to_s
|
63
|
+
attrs
|
64
|
+
end
|
65
|
+
|
66
|
+
super(key, attributes).tap do |record|
|
67
|
+
record.attributes_changed!(original_attributes.diff(attributes).keys)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
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
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Persistence
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
included do
|
5
|
+
class_inheritable_writer :write_consistency
|
6
|
+
class_inheritable_writer :read_consistency
|
7
|
+
end
|
8
|
+
|
9
|
+
VALID_READ_CONSISTENCY_LEVELS = [:one, :quorum, :all]
|
10
|
+
VALID_WRITE_CONSISTENCY_LEVELS = VALID_READ_CONSISTENCY_LEVELS + [:zero]
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def consistency_levels(levels)
|
14
|
+
if levels.has_key?(:write)
|
15
|
+
unless valid_write_consistency_level?(levels[:write])
|
16
|
+
raise ArgumentError, "Invalid write consistency level. Valid levels are: #{VALID_WRITE_CONSISTENCY_LEVELS.inspect}. You gave me #{levels[:write].inspect}"
|
17
|
+
end
|
18
|
+
self.write_consistency = levels[:write]
|
19
|
+
end
|
20
|
+
|
21
|
+
if levels.has_key?(:read)
|
22
|
+
unless valid_read_consistency_level?(levels[:read])
|
23
|
+
raise ArgumentError, "Invalid read consistency level. Valid levels are #{VALID_READ_CONSISTENCY_LEVELS.inspect}. You gave me #{levels[:write].inspect}"
|
24
|
+
end
|
25
|
+
self.read_consistency = levels[:read]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_consistency
|
30
|
+
read_inheritable_attribute(:write_consistency) || :quorum
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_consistency
|
34
|
+
read_inheritable_attribute(:read_consistency) || :quorum
|
35
|
+
end
|
36
|
+
|
37
|
+
def get(key, options = {})
|
38
|
+
multi_get([key], options).values.first
|
39
|
+
end
|
40
|
+
|
41
|
+
def multi_get(keys, options = {})
|
42
|
+
options = {:consistency => self.read_consistency, :limit => 100}.merge(options)
|
43
|
+
unless valid_read_consistency_level?(options[:consistency])
|
44
|
+
raise ArgumentError, "Invalid read consistency level: '#{options[:consistency]}'. Valid options are [:quorum, :one]"
|
45
|
+
end
|
46
|
+
|
47
|
+
attribute_results = ActiveSupport::Notifications.instrument("multi_get.cassandra_object", :keys => keys) do
|
48
|
+
connection.multi_get(column_family, keys.map(&:to_s), :count=>options[:limit], :consistency=>consistency_for_thrift(options[:consistency]))
|
49
|
+
end
|
50
|
+
|
51
|
+
attribute_results.inject(ActiveSupport::OrderedHash.new) do |memo, (key, attributes)|
|
52
|
+
if attributes.empty?
|
53
|
+
memo[key] = nil
|
54
|
+
else
|
55
|
+
memo[parse_key(key)] = instantiate(key, attributes)
|
56
|
+
end
|
57
|
+
memo
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def remove(key)
|
62
|
+
connection.remove(column_family, key.to_s, :consistency => write_consistency_for_thrift)
|
63
|
+
end
|
64
|
+
|
65
|
+
def all(keyrange = ''..'', options = {})
|
66
|
+
results = connection.get_range(column_family, :start => keyrange.first, :finish => keyrange.last, :count=>(options[:limit] || 100))
|
67
|
+
keys = results.map(&:key)
|
68
|
+
keys.map {|key| get(key) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def first(keyrange = ''..'', options = {})
|
72
|
+
all(keyrange, options.merge(:limit=>1)).first
|
73
|
+
end
|
74
|
+
|
75
|
+
def create(attributes)
|
76
|
+
new(attributes).tap do |object|
|
77
|
+
object.save
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def write(key, attributes, schema_version)
|
82
|
+
key.tap do |key|
|
83
|
+
connection.insert(column_family, key.to_s, encode_columns_hash(attributes, schema_version), :consistency => write_consistency_for_thrift)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def instantiate(key, attributes)
|
88
|
+
# remove any attributes we don't know about. we would do this earlier, but we want to make such
|
89
|
+
# attributes available to migrations
|
90
|
+
attributes.delete_if{|k,_| !model_attributes.keys.include?(k)}
|
91
|
+
allocate.tap do |object|
|
92
|
+
object.instance_variable_set("@schema_version", attributes.delete('schema_version'))
|
93
|
+
object.instance_variable_set("@key", parse_key(key))
|
94
|
+
object.instance_variable_set("@attributes", decode_columns_hash(attributes).with_indifferent_access)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def encode_columns_hash(attributes, schema_version)
|
99
|
+
attributes.inject(Hash.new) do |memo, (column_name, value)|
|
100
|
+
memo[column_name.to_s] = model_attributes[column_name].converter.encode(value)
|
101
|
+
memo
|
102
|
+
end.merge({"schema_version" => schema_version.to_s})
|
103
|
+
end
|
104
|
+
|
105
|
+
def decode_columns_hash(attributes)
|
106
|
+
attributes.inject(Hash.new) do |memo, (column_name, value)|
|
107
|
+
memo[column_name.to_s] = model_attributes[column_name].converter.decode(value)
|
108
|
+
memo
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def column_family_configuration
|
113
|
+
[{:Name=>column_family, :CompareWith=>"UTF8Type"}]
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
def valid_read_consistency_level?(level)
|
118
|
+
!!VALID_READ_CONSISTENCY_LEVELS.include?(level)
|
119
|
+
end
|
120
|
+
|
121
|
+
def valid_write_consistency_level?(level)
|
122
|
+
!!VALID_WRITE_CONSISTENCY_LEVELS.include?(level)
|
123
|
+
end
|
124
|
+
|
125
|
+
def write_consistency_for_thrift
|
126
|
+
consistency_for_thrift(write_consistency)
|
127
|
+
end
|
128
|
+
|
129
|
+
def read_consistency_for_thrift
|
130
|
+
consistency_for_thrift(read_consistency)
|
131
|
+
end
|
132
|
+
|
133
|
+
def consistency_for_thrift(consistency)
|
134
|
+
{
|
135
|
+
:zero => Cassandra::Consistency::ZERO,
|
136
|
+
:one => Cassandra::Consistency::ONE,
|
137
|
+
:quorum => Cassandra::Consistency::QUORUM,
|
138
|
+
:all => Cassandra::Consistency::ALL
|
139
|
+
}[consistency]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
module InstanceMethods
|
144
|
+
def save
|
145
|
+
run_callbacks :save do
|
146
|
+
create_or_update
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def create_or_update
|
151
|
+
if new_record?
|
152
|
+
create
|
153
|
+
else
|
154
|
+
update
|
155
|
+
end
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
159
|
+
def create
|
160
|
+
run_callbacks :create do
|
161
|
+
@key ||= self.class.next_key(self)
|
162
|
+
_write
|
163
|
+
@new_record = false
|
164
|
+
true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def update
|
169
|
+
run_callbacks :update do
|
170
|
+
_write
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def _write
|
175
|
+
changed_attributes = changed.inject({}) { |h, n| h[n] = read_attribute(n); h }
|
176
|
+
self.class.write(key, changed_attributes, schema_version)
|
177
|
+
end
|
178
|
+
|
179
|
+
def new_record?
|
180
|
+
@new_record || false
|
181
|
+
end
|
182
|
+
|
183
|
+
def destroy
|
184
|
+
run_callbacks :destroy do
|
185
|
+
self.class.remove(key)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def reload
|
190
|
+
self.class.get(self.key)
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|