cassandra_object 0.6.0.pre
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/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
|