gotime-cassandra_object 0.6.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.
- 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
|