cassandra_object 0.6.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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/associations.rb +35 -0
- data/lib/cassandra_object/attributes.rb +93 -0
- data/lib/cassandra_object/base.rb +104 -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/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/identity.rb +61 -0
- data/lib/cassandra_object/indexes.rb +129 -0
- data/lib/cassandra_object/legacy_callbacks.rb +33 -0
- data/lib/cassandra_object/migrations.rb +72 -0
- data/lib/cassandra_object/mocking.rb +15 -0
- data/lib/cassandra_object/persistence.rb +193 -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 +58 -0
- data/lib/cassandra_object.rb +30 -0
- data/vendor/active_support_shims.rb +4 -0
- data/vendor/activemodel/CHANGELOG +13 -0
- data/vendor/activemodel/CHANGES +12 -0
- data/vendor/activemodel/MIT-LICENSE +21 -0
- data/vendor/activemodel/README +21 -0
- data/vendor/activemodel/Rakefile +52 -0
- data/vendor/activemodel/activemodel.gemspec +19 -0
- data/vendor/activemodel/examples/validations.rb +29 -0
- data/vendor/activemodel/lib/active_model/attribute_methods.rb +291 -0
- data/vendor/activemodel/lib/active_model/callbacks.rb +91 -0
- data/vendor/activemodel/lib/active_model/conversion.rb +8 -0
- data/vendor/activemodel/lib/active_model/deprecated_error_methods.rb +33 -0
- data/vendor/activemodel/lib/active_model/dirty.rb +126 -0
- data/vendor/activemodel/lib/active_model/errors.rb +162 -0
- data/vendor/activemodel/lib/active_model/lint.rb +91 -0
- data/vendor/activemodel/lib/active_model/locale/en.yml +27 -0
- data/vendor/activemodel/lib/active_model/naming.rb +45 -0
- data/vendor/activemodel/lib/active_model/observing.rb +191 -0
- data/vendor/activemodel/lib/active_model/railtie.rb +2 -0
- data/vendor/activemodel/lib/active_model/serialization.rb +30 -0
- data/vendor/activemodel/lib/active_model/serializers/json.rb +96 -0
- data/vendor/activemodel/lib/active_model/serializers/xml.rb +204 -0
- data/vendor/activemodel/lib/active_model/state_machine/event.rb +62 -0
- data/vendor/activemodel/lib/active_model/state_machine/machine.rb +75 -0
- data/vendor/activemodel/lib/active_model/state_machine/state.rb +47 -0
- data/vendor/activemodel/lib/active_model/state_machine/state_transition.rb +40 -0
- data/vendor/activemodel/lib/active_model/state_machine.rb +70 -0
- data/vendor/activemodel/lib/active_model/test_case.rb +18 -0
- data/vendor/activemodel/lib/active_model/translation.rb +44 -0
- data/vendor/activemodel/lib/active_model/validations/acceptance.rb +55 -0
- data/vendor/activemodel/lib/active_model/validations/confirmation.rb +47 -0
- data/vendor/activemodel/lib/active_model/validations/exclusion.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/format.rb +64 -0
- data/vendor/activemodel/lib/active_model/validations/inclusion.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/length.rb +117 -0
- data/vendor/activemodel/lib/active_model/validations/numericality.rb +111 -0
- data/vendor/activemodel/lib/active_model/validations/presence.rb +42 -0
- data/vendor/activemodel/lib/active_model/validations/with.rb +59 -0
- data/vendor/activemodel/lib/active_model/validations.rb +120 -0
- data/vendor/activemodel/lib/active_model/validator.rb +110 -0
- data/vendor/activemodel/lib/active_model/version.rb +9 -0
- data/vendor/activemodel/lib/active_model.rb +61 -0
- data/vendor/activemodel/test/cases/attribute_methods_test.rb +46 -0
- data/vendor/activemodel/test/cases/callbacks_test.rb +70 -0
- data/vendor/activemodel/test/cases/helper.rb +23 -0
- data/vendor/activemodel/test/cases/lint_test.rb +28 -0
- data/vendor/activemodel/test/cases/naming_test.rb +28 -0
- data/vendor/activemodel/test/cases/observing_test.rb +133 -0
- data/vendor/activemodel/test/cases/serializeration/json_serialization_test.rb +83 -0
- data/vendor/activemodel/test/cases/serializeration/xml_serialization_test.rb +110 -0
- data/vendor/activemodel/test/cases/state_machine/event_test.rb +49 -0
- data/vendor/activemodel/test/cases/state_machine/machine_test.rb +43 -0
- data/vendor/activemodel/test/cases/state_machine/state_test.rb +72 -0
- data/vendor/activemodel/test/cases/state_machine/state_transition_test.rb +84 -0
- data/vendor/activemodel/test/cases/state_machine_test.rb +312 -0
- data/vendor/activemodel/test/cases/tests_database.rb +37 -0
- data/vendor/activemodel/test/cases/translation_test.rb +45 -0
- data/vendor/activemodel/test/cases/validations/acceptance_validation_test.rb +71 -0
- data/vendor/activemodel/test/cases/validations/conditional_validation_test.rb +141 -0
- data/vendor/activemodel/test/cases/validations/confirmation_validation_test.rb +58 -0
- data/vendor/activemodel/test/cases/validations/exclusion_validation_test.rb +47 -0
- data/vendor/activemodel/test/cases/validations/format_validation_test.rb +118 -0
- data/vendor/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +175 -0
- data/vendor/activemodel/test/cases/validations/i18n_validation_test.rb +527 -0
- data/vendor/activemodel/test/cases/validations/inclusion_validation_test.rb +71 -0
- data/vendor/activemodel/test/cases/validations/length_validation_test.rb +437 -0
- data/vendor/activemodel/test/cases/validations/numericality_validation_test.rb +180 -0
- data/vendor/activemodel/test/cases/validations/presence_validation_test.rb +70 -0
- data/vendor/activemodel/test/cases/validations/with_validation_test.rb +166 -0
- data/vendor/activemodel/test/cases/validations_test.rb +215 -0
- data/vendor/activemodel/test/config.rb +3 -0
- data/vendor/activemodel/test/fixtures/topics.yml +41 -0
- data/vendor/activemodel/test/models/contact.rb +7 -0
- data/vendor/activemodel/test/models/custom_reader.rb +17 -0
- data/vendor/activemodel/test/models/developer.rb +6 -0
- data/vendor/activemodel/test/models/person.rb +9 -0
- data/vendor/activemodel/test/models/reply.rb +34 -0
- data/vendor/activemodel/test/models/topic.rb +9 -0
- data/vendor/activemodel/test/models/track_back.rb +4 -0
- data/vendor/activemodel/test/schema.rb +14 -0
- data/vendor/activesupport/lib/active_support/autoload.rb +48 -0
- data/vendor/activesupport/lib/active_support/concern.rb +25 -0
- data/vendor/activesupport/lib/active_support/core_ext/array/wrap.rb +20 -0
- data/vendor/activesupport/lib/active_support/core_ext/object/blank.rb +58 -0
- data/vendor/activesupport/lib/active_support/core_ext/object/tap.rb +6 -0
- data/vendor/activesupport/lib/active_support/dependency_module.rb +17 -0
- data/vendor/activesupport/lib/active_support/i18n.rb +2 -0
- data/vendor/activesupport/lib/active_support/locale/en.yml +33 -0
- metadata +230 -0
@@ -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,61 @@
|
|
1
|
+
require 'cassandra_object/identity/abstract_key_factory'
|
2
|
+
require 'cassandra_object/identity/key'
|
3
|
+
require 'cassandra_object/identity/uuid_key_factory'
|
4
|
+
require 'cassandra_object/identity/natural_key_factory'
|
5
|
+
|
6
|
+
module CassandraObject
|
7
|
+
# Some docs will be needed here but the gist of this is simple. Instead of returning a string, Base#key now returns a key object.
|
8
|
+
# There are corresponding key factories which generate them
|
9
|
+
module Identity
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
module ClassMethods
|
12
|
+
# Indicate what kind of key the model will have: uuid or natural
|
13
|
+
#
|
14
|
+
# @param [:uuid, :natural] the type of key
|
15
|
+
# @param the options you want to pass along to the key factory (like :attributes => :name, for a natural key).
|
16
|
+
#
|
17
|
+
def key(name_or_factory = :uuid, *options)
|
18
|
+
@key_factory = case name_or_factory
|
19
|
+
when :uuid
|
20
|
+
UUIDKeyFactory.new
|
21
|
+
when :natural
|
22
|
+
NaturalKeyFactory.new(*options)
|
23
|
+
else
|
24
|
+
name_or_factory
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_key(object = nil)
|
29
|
+
returning(@key_factory.next_key(object)) do |key|
|
30
|
+
raise "Keys may not be nil" if key.nil?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_key(string)
|
35
|
+
@key_factory.parse(string)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
|
41
|
+
def ==(comparison_object)
|
42
|
+
comparison_object.equal?(self) ||
|
43
|
+
(comparison_object.instance_of?(self.class) &&
|
44
|
+
comparison_object.key == key &&
|
45
|
+
!comparison_object.new_record?)
|
46
|
+
end
|
47
|
+
|
48
|
+
def eql?(comparison_object)
|
49
|
+
self == (comparison_object)
|
50
|
+
end
|
51
|
+
|
52
|
+
def hash
|
53
|
+
key.to_s.hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_param
|
57
|
+
key.to_param
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -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,33 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Callbacks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveSupport::Callbacks
|
5
|
+
|
6
|
+
included do
|
7
|
+
define_model_callbacks :save, :create, :destroy, :update
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def define_model_callbacks(*callbacks)
|
12
|
+
callbacks.each do |callback|
|
13
|
+
define_callbacks "before_#{callback}"
|
14
|
+
define_callbacks "after_#{callback}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
def run_callbacks(callback)
|
21
|
+
if block_given?
|
22
|
+
unless false == super("before_#{callback}")
|
23
|
+
yield.tap do
|
24
|
+
super("after_#{callback}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -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
|
+
returning super(key, attributes) 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,193 @@
|
|
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 = connection.multi_get(column_family, keys.map(&:to_s), :count=>options[:limit], :consistency=>consistency_for_thrift(options[:consistency]))
|
48
|
+
|
49
|
+
attribute_results.inject(ActiveSupport::OrderedHash.new) do |memo, (key, attributes)|
|
50
|
+
if attributes.empty?
|
51
|
+
memo[key] = nil
|
52
|
+
else
|
53
|
+
memo[parse_key(key)] = instantiate(key, attributes)
|
54
|
+
end
|
55
|
+
memo
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def remove(key)
|
60
|
+
connection.remove(column_family, key.to_s, :consistency => write_consistency_for_thrift)
|
61
|
+
end
|
62
|
+
|
63
|
+
def all(keyrange = ''..'', options = {})
|
64
|
+
results = connection.get_range(column_family, :start => keyrange.first, :finish => keyrange.last, :count=>(options[:limit] || 100))
|
65
|
+
keys = results.map(&:key)
|
66
|
+
keys.map {|key| get(key) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def first(keyrange = ''..'', options = {})
|
70
|
+
all(keyrange, options.merge(:limit=>1)).first
|
71
|
+
end
|
72
|
+
|
73
|
+
def create(attributes)
|
74
|
+
returning new(attributes) do |object|
|
75
|
+
object.save
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def write(key, attributes, schema_version)
|
80
|
+
returning(key) do |key|
|
81
|
+
connection.insert(column_family, key.to_s, encode_columns_hash(attributes, schema_version), :consistency => write_consistency_for_thrift)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def instantiate(key, attributes)
|
86
|
+
# remove any attributes we don't know about. we would do this earlier, but we want to make such
|
87
|
+
# attributes available to migrations
|
88
|
+
attributes.delete_if{|k,_| !model_attributes.keys.include?(k)}
|
89
|
+
returning allocate do |object|
|
90
|
+
object.instance_variable_set("@schema_version", attributes.delete('schema_version'))
|
91
|
+
object.instance_variable_set("@key", parse_key(key))
|
92
|
+
object.instance_variable_set("@attributes", decode_columns_hash(attributes).with_indifferent_access)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def encode_columns_hash(attributes, schema_version)
|
97
|
+
attributes.inject(Hash.new) do |memo, (column_name, value)|
|
98
|
+
memo[column_name.to_s] = model_attributes[column_name].converter.encode(value)
|
99
|
+
memo
|
100
|
+
end.merge({"schema_version" => schema_version.to_s})
|
101
|
+
end
|
102
|
+
|
103
|
+
def decode_columns_hash(attributes)
|
104
|
+
attributes.inject(Hash.new) do |memo, (column_name, value)|
|
105
|
+
memo[column_name.to_s] = model_attributes[column_name].converter.decode(value)
|
106
|
+
memo
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def column_family_configuration
|
111
|
+
[{:Name=>column_family, :CompareWith=>"UTF8Type"}]
|
112
|
+
end
|
113
|
+
|
114
|
+
protected
|
115
|
+
def valid_read_consistency_level?(level)
|
116
|
+
!!VALID_READ_CONSISTENCY_LEVELS.include?(level)
|
117
|
+
end
|
118
|
+
|
119
|
+
def valid_write_consistency_level?(level)
|
120
|
+
!!VALID_WRITE_CONSISTENCY_LEVELS.include?(level)
|
121
|
+
end
|
122
|
+
|
123
|
+
def write_consistency_for_thrift
|
124
|
+
consistency_for_thrift(write_consistency)
|
125
|
+
end
|
126
|
+
|
127
|
+
def read_consistency_for_thrift
|
128
|
+
consistency_for_thrift(read_consistency)
|
129
|
+
end
|
130
|
+
|
131
|
+
def consistency_for_thrift(consistency)
|
132
|
+
{
|
133
|
+
:zero => Cassandra::Consistency::ZERO,
|
134
|
+
:one => Cassandra::Consistency::ONE,
|
135
|
+
:quorum => Cassandra::Consistency::QUORUM,
|
136
|
+
:all => Cassandra::Consistency::ALL
|
137
|
+
}[consistency]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module InstanceMethods
|
142
|
+
def save
|
143
|
+
run_callbacks :save do
|
144
|
+
create_or_update
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_or_update
|
149
|
+
if new_record?
|
150
|
+
create
|
151
|
+
else
|
152
|
+
update
|
153
|
+
end
|
154
|
+
true
|
155
|
+
end
|
156
|
+
|
157
|
+
def create
|
158
|
+
run_callbacks :create do
|
159
|
+
@key ||= self.class.next_key(self)
|
160
|
+
_write
|
161
|
+
@new_record = false
|
162
|
+
true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def update
|
167
|
+
run_callbacks :update do
|
168
|
+
_write
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def _write
|
173
|
+
changed_attributes = changed.inject({}) { |h, n| h[n] = read_attribute(n); h }
|
174
|
+
self.class.write(key, changed_attributes, schema_version)
|
175
|
+
end
|
176
|
+
|
177
|
+
def new_record?
|
178
|
+
@new_record || false
|
179
|
+
end
|
180
|
+
|
181
|
+
def destroy
|
182
|
+
run_callbacks :destroy do
|
183
|
+
self.class.remove(key)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def reload
|
188
|
+
self.class.get(self.key)
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
CassandraObject::Base.register_attribute_type(:integer, Integer, CassandraObject::IntegerType)
|
2
|
+
CassandraObject::Base.register_attribute_type(:float, Float, CassandraObject::FloatType)
|
3
|
+
CassandraObject::Base.register_attribute_type(:date, Date, CassandraObject::DateType)
|
4
|
+
CassandraObject::Base.register_attribute_type(:time, Time, CassandraObject::TimeType)
|
5
|
+
CassandraObject::Base.register_attribute_type(:time_with_zone, ActiveSupport::TimeWithZone, CassandraObject::TimeWithZoneType)
|
6
|
+
CassandraObject::Base.register_attribute_type(:string, String, CassandraObject::StringType)
|
7
|
+
CassandraObject::Base.register_attribute_type(:hash, Hash, CassandraObject::HashType)
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module IntegerType
|
3
|
+
REGEX = /\A[-+]?\d+\Z/
|
4
|
+
def encode(int)
|
5
|
+
return '' if int.nil?
|
6
|
+
raise ArgumentError.new("#{self} requires an Integer. You passed #{int.inspect}") unless int.kind_of?(Integer)
|
7
|
+
int.to_s
|
8
|
+
end
|
9
|
+
module_function :encode
|
10
|
+
|
11
|
+
def decode(str)
|
12
|
+
return nil if str.empty?
|
13
|
+
raise ArgumentError.new("#{str} isn't a String that looks like a Integer") unless str.kind_of?(String) && str.match(REGEX)
|
14
|
+
str.to_i
|
15
|
+
end
|
16
|
+
module_function :decode
|
17
|
+
end
|
18
|
+
|
19
|
+
module FloatType
|
20
|
+
REGEX = /\A[-+]?\d+(\.\d+)\Z/
|
21
|
+
def encode(float)
|
22
|
+
return '' if float.nil?
|
23
|
+
raise ArgumentError.new("#{self} requires a Float") unless float.kind_of?(Float)
|
24
|
+
float.to_s
|
25
|
+
end
|
26
|
+
module_function :encode
|
27
|
+
|
28
|
+
def decode(str)
|
29
|
+
return nil if str == ''
|
30
|
+
raise ArgumentError.new("#{str} isn't a String that looks like a Float") unless str.kind_of?(String) && str.match(REGEX)
|
31
|
+
str.to_f
|
32
|
+
end
|
33
|
+
module_function :decode
|
34
|
+
end
|
35
|
+
|
36
|
+
module DateType
|
37
|
+
FORMAT = '%Y-%m-%d'
|
38
|
+
REGEX = /\A\d{4}-\d{2}-\d{2}\Z/
|
39
|
+
def encode(date)
|
40
|
+
raise ArgumentError.new("#{self} requires a Date") unless date.kind_of?(Date)
|
41
|
+
date.strftime(FORMAT)
|
42
|
+
end
|
43
|
+
module_function :encode
|
44
|
+
|
45
|
+
def decode(str)
|
46
|
+
raise ArgumentError.new("#{str} isn't a String that looks like a Date") unless str.kind_of?(String) && str.match(REGEX)
|
47
|
+
Date.strptime(str, FORMAT)
|
48
|
+
end
|
49
|
+
module_function :decode
|
50
|
+
end
|
51
|
+
|
52
|
+
module TimeType
|
53
|
+
# lifted from the implementation of Time.xmlschema and simplified
|
54
|
+
REGEX = /\A\s*
|
55
|
+
(-?\d+)-(\d\d)-(\d\d)
|
56
|
+
T
|
57
|
+
(\d\d):(\d\d):(\d\d)
|
58
|
+
(\.\d*)?
|
59
|
+
(Z|[+-]\d\d:\d\d)?
|
60
|
+
\s*\z/ix
|
61
|
+
|
62
|
+
def encode(time)
|
63
|
+
raise ArgumentError.new("#{self} requires a Time") unless time.kind_of?(Time)
|
64
|
+
time.xmlschema(6)
|
65
|
+
end
|
66
|
+
module_function :encode
|
67
|
+
|
68
|
+
def decode(str)
|
69
|
+
raise ArgumentError.new("#{str} isn't a String that looks like a Time") unless str.kind_of?(String) && str.match(REGEX)
|
70
|
+
Time.xmlschema(str)
|
71
|
+
end
|
72
|
+
module_function :decode
|
73
|
+
end
|
74
|
+
|
75
|
+
module TimeWithZoneType
|
76
|
+
def encode(time)
|
77
|
+
TimeType.encode(time.utc)
|
78
|
+
end
|
79
|
+
module_function :encode
|
80
|
+
|
81
|
+
def decode(str)
|
82
|
+
TimeType.decode(str).in_time_zone
|
83
|
+
end
|
84
|
+
module_function :decode
|
85
|
+
end
|
86
|
+
|
87
|
+
module StringType
|
88
|
+
def encode(str)
|
89
|
+
raise ArgumentError.new("#{self} requires a String") unless str.kind_of?(String)
|
90
|
+
str
|
91
|
+
end
|
92
|
+
module_function :encode
|
93
|
+
|
94
|
+
def decode(str)
|
95
|
+
str
|
96
|
+
end
|
97
|
+
module_function :decode
|
98
|
+
end
|
99
|
+
|
100
|
+
module HashType
|
101
|
+
def encode(hash)
|
102
|
+
raise ArgumentError.new("#{self} requires a Hash") unless hash.kind_of?(Hash)
|
103
|
+
ActiveSupport::JSON.encode(hash)
|
104
|
+
end
|
105
|
+
module_function :encode
|
106
|
+
|
107
|
+
def decode(str)
|
108
|
+
ActiveSupport::JSON.decode(str)
|
109
|
+
end
|
110
|
+
module_function :decode
|
111
|
+
end
|
112
|
+
|
113
|
+
module BooleanType
|
114
|
+
ALLOWED = [true, false, nil]
|
115
|
+
def encode(bool)
|
116
|
+
unless ALLOWED.any?{ |a| bool == a }
|
117
|
+
raise ArgumentError.new("#{self} requires a Boolean or nil")
|
118
|
+
end
|
119
|
+
bool ? '1' : '0'
|
120
|
+
end
|
121
|
+
module_function :encode
|
122
|
+
|
123
|
+
def decode(bool)
|
124
|
+
bool == '1'
|
125
|
+
end
|
126
|
+
module_function :decode
|
127
|
+
end
|
128
|
+
end
|