sessionm-cassandra_object 2.2.6
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/.gitignore +2 -0
- data/CHANGELOG +3 -0
- data/Gemfile +2 -0
- data/LICENSE +13 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +12 -0
- data/Rakefile +15 -0
- data/lib/cassandra_object/associations/one_to_many.rb +146 -0
- data/lib/cassandra_object/associations/one_to_one.rb +85 -0
- data/lib/cassandra_object/associations.rb +50 -0
- data/lib/cassandra_object/attributes.rb +97 -0
- data/lib/cassandra_object/base.rb +97 -0
- data/lib/cassandra_object/batches.rb +31 -0
- data/lib/cassandra_object/callbacks.rb +27 -0
- data/lib/cassandra_object/collection.rb +8 -0
- data/lib/cassandra_object/connection.rb +29 -0
- data/lib/cassandra_object/consistency.rb +31 -0
- data/lib/cassandra_object/cursor.rb +90 -0
- data/lib/cassandra_object/dirty.rb +32 -0
- data/lib/cassandra_object/errors.rb +10 -0
- data/lib/cassandra_object/finder_methods.rb +72 -0
- data/lib/cassandra_object/generators/migration_generator.rb +31 -0
- data/lib/cassandra_object/generators/templates/migration.rb.erb +11 -0
- data/lib/cassandra_object/identity/abstract_key_factory.rb +36 -0
- data/lib/cassandra_object/identity/custom_key_factory.rb +50 -0
- data/lib/cassandra_object/identity/hashed_natural_key_factory.rb +10 -0
- data/lib/cassandra_object/identity/key.rb +20 -0
- data/lib/cassandra_object/identity/natural_key_factory.rb +51 -0
- data/lib/cassandra_object/identity/uuid_key_factory.rb +39 -0
- data/lib/cassandra_object/identity.rb +52 -0
- data/lib/cassandra_object/log_subscriber.rb +37 -0
- data/lib/cassandra_object/migrations/migration.rb +15 -0
- data/lib/cassandra_object/migrations.rb +66 -0
- data/lib/cassandra_object/mocking.rb +15 -0
- data/lib/cassandra_object/persistence.rb +138 -0
- data/lib/cassandra_object/railtie.rb +11 -0
- data/lib/cassandra_object/schema/migration.rb +106 -0
- data/lib/cassandra_object/schema/migration_proxy.rb +25 -0
- data/lib/cassandra_object/schema/migrator.rb +213 -0
- data/lib/cassandra_object/schema.rb +37 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/tasks/column_family.rb +90 -0
- data/lib/cassandra_object/tasks/keyspace.rb +89 -0
- data/lib/cassandra_object/tasks/ks.rake +121 -0
- data/lib/cassandra_object/timestamps.rb +19 -0
- data/lib/cassandra_object/type.rb +19 -0
- data/lib/cassandra_object/types/array_type.rb +16 -0
- data/lib/cassandra_object/types/boolean_type.rb +23 -0
- data/lib/cassandra_object/types/date_type.rb +20 -0
- data/lib/cassandra_object/types/float_type.rb +19 -0
- data/lib/cassandra_object/types/hash_type.rb +16 -0
- data/lib/cassandra_object/types/integer_type.rb +19 -0
- data/lib/cassandra_object/types/set_type.rb +22 -0
- data/lib/cassandra_object/types/string_type.rb +16 -0
- data/lib/cassandra_object/types/time_type.rb +27 -0
- data/lib/cassandra_object/types/time_with_zone_type.rb +18 -0
- data/lib/cassandra_object/types/utf8_string_type.rb +18 -0
- data/lib/cassandra_object/types.rb +11 -0
- data/lib/cassandra_object/validations.rb +46 -0
- data/lib/cassandra_object.rb +49 -0
- data/sessionm-cassandra_object.gemspec +26 -0
- data/test/active_model_test.rb +9 -0
- data/test/base_test.rb +28 -0
- data/test/batches_test.rb +30 -0
- data/test/connection_test.rb +28 -0
- data/test/consistency_test.rb +20 -0
- data/test/finder_methods_test.rb +49 -0
- data/test/identity_test.rb +30 -0
- data/test/persistence_test.rb +84 -0
- data/test/test_helper.rb +31 -0
- data/test/timestamps_test.rb +27 -0
- data/test/types/array_type_test.rb +15 -0
- data/test/types/boolean_type_test.rb +23 -0
- data/test/types/date_type_test.rb +4 -0
- data/test/types/float_type_test.rb +4 -0
- data/test/types/hash_type_test.rb +4 -0
- data/test/types/integer_type_test.rb +18 -0
- data/test/types/set_type_test.rb +17 -0
- data/test/types/string_type_test.rb +4 -0
- data/test/types/time_type_test.rb +4 -0
- data/test/types/utf8_string_type_test.rb +4 -0
- data/test/validations_test.rb +15 -0
- metadata +183 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2009 Koziarski Software Ltd
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 [Michael Koziarski]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Cassandra Object
|
2
|
+
|
3
|
+
Cassandra Object provides a API for working with [Cassandra](http://incubator.apache.org/cassandra/) in Rails projects.
|
4
|
+
|
5
|
+
Installation:
|
6
|
+
|
7
|
+
gem 'gotime-cassandra_object', require: 'cassandra_object'
|
8
|
+
|
9
|
+
Example:
|
10
|
+
|
11
|
+
class Customer < CassandraObject::Base
|
12
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
# require File.expand_path('../lib/cassandra_object', __FILE__)
|
7
|
+
|
8
|
+
task default: :test
|
9
|
+
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.libs << 'test'
|
13
|
+
t.pattern = 'test/**/*_test.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Associations
|
3
|
+
class OneToMany
|
4
|
+
def initialize(association_name, owner_class, options)
|
5
|
+
@association_name = association_name.to_s
|
6
|
+
@owner_class = owner_class
|
7
|
+
@target_class_name = options[:class_name] || association_name.to_s.singularize.camelize
|
8
|
+
@options = options
|
9
|
+
|
10
|
+
define_methods!
|
11
|
+
end
|
12
|
+
|
13
|
+
def find(owner, options = {})
|
14
|
+
reversed = options.has_key?(:reversed) ? options[:reversed] : reversed?
|
15
|
+
cursor = CassandraObject::Cursor.new(target_class, column_family, owner.key.to_s, @association_name, :start_after => options[:start_after], :reversed => reversed)
|
16
|
+
cursor.find(options[:limit] || 100)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(owner, record, set_inverse = true)
|
20
|
+
key = owner.key
|
21
|
+
attributes = {@association_name=>{new_key=>record.key.to_s}}
|
22
|
+
ActiveSupport::Notifications.instrument("insert.cassandra_object", :column_family => column_family, :key => key, :attributes => attributes) do
|
23
|
+
connection.insert(column_family, key.to_s, attributes, :consistency => @owner_class.thrift_write_consistency)
|
24
|
+
end
|
25
|
+
if has_inverse? && set_inverse
|
26
|
+
inverse.set_inverse(record, owner)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def new_key
|
31
|
+
SimpleUUID::UUID.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def column_family
|
35
|
+
@owner_class.relationships_column_family
|
36
|
+
end
|
37
|
+
|
38
|
+
def connection
|
39
|
+
@owner_class.connection
|
40
|
+
end
|
41
|
+
|
42
|
+
def target_class
|
43
|
+
@target_class ||= @target_class_name.constantize
|
44
|
+
end
|
45
|
+
|
46
|
+
def new_proxy(owner)
|
47
|
+
OneToManyAssociationProxy.new(self, owner)
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_inverse?
|
51
|
+
@options[:inverse_of]
|
52
|
+
end
|
53
|
+
|
54
|
+
def inverse
|
55
|
+
has_inverse? && target_class.associations[@options[:inverse_of]]
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_inverse(owner, record)
|
59
|
+
add(owner, record, false)
|
60
|
+
end
|
61
|
+
|
62
|
+
def reversed?
|
63
|
+
@options[:reversed] == true
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_methods!
|
67
|
+
@owner_class.class_eval <<-eos
|
68
|
+
def #{@association_name}
|
69
|
+
@_#{@association_name} ||= self.class.associations[:#{@association_name}].new_proxy(self)
|
70
|
+
end
|
71
|
+
eos
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class OneToManyAssociationProxy
|
76
|
+
def initialize(association, owner)
|
77
|
+
@association = association
|
78
|
+
@owner = owner
|
79
|
+
end
|
80
|
+
|
81
|
+
include Enumerable
|
82
|
+
def each
|
83
|
+
target.each do |i|
|
84
|
+
yield i
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](index)
|
89
|
+
to_a[index]
|
90
|
+
end
|
91
|
+
|
92
|
+
def <<(record)
|
93
|
+
@association.add(@owner, record)
|
94
|
+
if loaded?
|
95
|
+
@target << record
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get the targets of this association proxy
|
100
|
+
#
|
101
|
+
# @param [Hash] options the options with which to modify this query
|
102
|
+
# @option options [String] :start_after the key after which to start returning results
|
103
|
+
# @option options [Boolean] :reversed (false or association default) return the results in reverse order
|
104
|
+
# @option options [Integer] :limit the max number of results to return
|
105
|
+
# @return [Array<CassandraObject::Base>] an array of objects of type self#target_class
|
106
|
+
#
|
107
|
+
def all(options = {})
|
108
|
+
@association.find(@owner, options)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Create a record of the associated type with
|
112
|
+
# the supplied attributes and add it to this
|
113
|
+
# association
|
114
|
+
#
|
115
|
+
# @param [Hash] attributes the attributes with which to create the object
|
116
|
+
# @return [CassandraObject::Base] the newly created object
|
117
|
+
#
|
118
|
+
def create(attributes)
|
119
|
+
@association.target_class.create(attributes).tap do |record|
|
120
|
+
if record.valid?
|
121
|
+
self << record
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def create!(attributes)
|
127
|
+
@association.target_class.create!(attributes).tap do |record|
|
128
|
+
self << record
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def target
|
133
|
+
@target ||= begin
|
134
|
+
@loaded = true
|
135
|
+
@association.find(@owner)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
alias to_a target
|
140
|
+
|
141
|
+
def loaded?
|
142
|
+
defined?(@loaded) && @loaded
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Associations
|
3
|
+
class OneToOne
|
4
|
+
def initialize(association_name, owner_class, options)
|
5
|
+
@association_name = association_name.to_s
|
6
|
+
@owner_class = owner_class
|
7
|
+
@target_class_name = options[:class_name] || association_name.to_s.camelize
|
8
|
+
@options = options
|
9
|
+
|
10
|
+
define_methods!
|
11
|
+
end
|
12
|
+
|
13
|
+
def define_methods!
|
14
|
+
@owner_class.class_eval <<-eos
|
15
|
+
def #{@association_name}
|
16
|
+
@_#{@association_name} ||= self.class.associations[:#{@association_name}].find(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def #{@association_name}=(record)
|
20
|
+
@_#{@association_name} = record
|
21
|
+
self.class.associations[:#{@association_name}].set(self, record)
|
22
|
+
end
|
23
|
+
eos
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear(owner)
|
27
|
+
ActiveSupport::Notifications.instrument("remove.cassandra_object", :column_family => column_family, :key => owner.key, :columns => @association_name) do
|
28
|
+
connection.remove(column_family, owner.key.to_s, @association_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def find(owner)
|
33
|
+
if key = connection.get(column_family, owner.key.to_s, @association_name.to_s, :count=>1).values.first
|
34
|
+
target_class.get(key)
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def set(owner, record, set_inverse = true)
|
41
|
+
clear(owner)
|
42
|
+
key = owner.key
|
43
|
+
attributes = {@association_name=>{new_key=>record.key.to_s}}
|
44
|
+
ActiveSupport::Notifications.instrument("insert.cassandra_object", :column_family => column_family, :key => key, :attributes => attributes) do
|
45
|
+
connection.insert(column_family, key.to_s, attributes, :consistency => @owner_class.thrift_write_consistency)
|
46
|
+
end
|
47
|
+
if has_inverse? && set_inverse
|
48
|
+
inverse.set_inverse(record, owner)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_key
|
53
|
+
SimpleUUID::UUID.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_inverse(owner, record)
|
57
|
+
set(owner, record, false)
|
58
|
+
end
|
59
|
+
|
60
|
+
def has_inverse?
|
61
|
+
@options[:inverse_of]
|
62
|
+
end
|
63
|
+
|
64
|
+
def inverse
|
65
|
+
has_inverse? && target_class.associations[@options[:inverse_of]]
|
66
|
+
end
|
67
|
+
|
68
|
+
def column_family
|
69
|
+
@owner_class.relationships_column_family
|
70
|
+
end
|
71
|
+
|
72
|
+
def connection
|
73
|
+
@owner_class.connection
|
74
|
+
end
|
75
|
+
|
76
|
+
def target_class
|
77
|
+
@target_class ||= @target_class_name.constantize
|
78
|
+
end
|
79
|
+
|
80
|
+
def new_proxy(owner)
|
81
|
+
# OneToManyAssociationProxy.new(self, owner)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Associations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
# TODO: is this the convention?
|
7
|
+
include ActiveRecord::Reflection
|
8
|
+
include ActiveRecord::Associations
|
9
|
+
|
10
|
+
autoload :OneToMany
|
11
|
+
autoload :OneToOne
|
12
|
+
|
13
|
+
included do
|
14
|
+
class_inheritable_hash :associations
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def relationships_column_family=(column_family)
|
19
|
+
@relationships_column_family = column_family
|
20
|
+
end
|
21
|
+
|
22
|
+
def relationships_column_family
|
23
|
+
@relationships_column_family || "#{name}Relationships"
|
24
|
+
end
|
25
|
+
|
26
|
+
def column_family_configuration
|
27
|
+
super << {:Name=>relationships_column_family, :CompareWith=>"UTF8Type", :CompareSubcolumnsWith=>"TimeUUIDType", :ColumnType=>"Super"}
|
28
|
+
end
|
29
|
+
|
30
|
+
def association(association_name, options= {})
|
31
|
+
if options[:unique]
|
32
|
+
write_inheritable_hash(:associations, {association_name => OneToOne.new(association_name, self, options)})
|
33
|
+
else
|
34
|
+
write_inheritable_hash(:associations, {association_name => OneToMany.new(association_name, self, options)})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove(key)
|
39
|
+
begin
|
40
|
+
ActiveSupport::Notifications.instrument("remove.cassandra_object", column_family: relationships_column_family, key: key) do
|
41
|
+
connection.remove(relationships_column_family, key.to_s, consistency: thrift_write_consistency)
|
42
|
+
end
|
43
|
+
rescue Cassandra::AccessError => e
|
44
|
+
raise e unless e.message =~ /Invalid column family/
|
45
|
+
end
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class Attribute
|
3
|
+
attr_reader :name, :converter, :expected_type
|
4
|
+
def initialize(name, converter, expected_type)
|
5
|
+
@name = name.to_s
|
6
|
+
@converter = converter
|
7
|
+
@expected_type = expected_type
|
8
|
+
end
|
9
|
+
|
10
|
+
def check_value!(value)
|
11
|
+
return value if value.nil?
|
12
|
+
value.kind_of?(expected_type) ? value : converter.decode(value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Attributes
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
include ActiveModel::AttributeMethods
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def attribute(name, options)
|
22
|
+
if model_attributes.empty?
|
23
|
+
self.model_attributes = {}.with_indifferent_access
|
24
|
+
end
|
25
|
+
|
26
|
+
if type_mapping = CassandraObject::Type.get_mapping(options[:type])
|
27
|
+
converter = type_mapping.converter
|
28
|
+
expected_type = type_mapping.expected_type
|
29
|
+
elsif options[:converter]
|
30
|
+
converter = options[:converter]
|
31
|
+
expected_type = options[:type]
|
32
|
+
else
|
33
|
+
raise "Unknown type #{options[:type]}"
|
34
|
+
end
|
35
|
+
|
36
|
+
model_attributes[name] = Attribute.new(name, converter, expected_type)
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_attribute_methods
|
40
|
+
super(model_attributes.keys)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
included do
|
45
|
+
class_attribute :model_attributes
|
46
|
+
self.model_attributes = {}.with_indifferent_access
|
47
|
+
|
48
|
+
attribute_method_suffix("", "=")
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_attribute(name, value)
|
52
|
+
if ma = self.class.model_attributes[name]
|
53
|
+
@attributes[name.to_s] = ma.check_value!(value)
|
54
|
+
else
|
55
|
+
raise NoMethodError, "Unknown attribute #{name.inspect}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def read_attribute(name)
|
60
|
+
@attributes[name.to_s]
|
61
|
+
end
|
62
|
+
|
63
|
+
def attributes=(attributes)
|
64
|
+
attributes.each do |(name, value)|
|
65
|
+
send("#{name}=", value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def method_missing(method_id, *args, &block)
|
70
|
+
if !self.class.attribute_methods_generated?
|
71
|
+
self.class.define_attribute_methods
|
72
|
+
send(method_id, *args, &block)
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def respond_to?(*args)
|
79
|
+
self.class.define_attribute_methods unless self.class.attribute_methods_generated?
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
def attribute_method?(name)
|
85
|
+
!!model_attributes[name.to_sym]
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def attribute(name)
|
90
|
+
read_attribute(name)
|
91
|
+
end
|
92
|
+
|
93
|
+
def attribute=(name, value)
|
94
|
+
write_attribute(name, value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
require 'cassandra_object/log_subscriber'
|
4
|
+
require 'cassandra_object/types'
|
5
|
+
require 'cassandra_object/errors'
|
6
|
+
|
7
|
+
module CassandraObject
|
8
|
+
class Base
|
9
|
+
class << self
|
10
|
+
def column_family=(column_family)
|
11
|
+
@column_family = column_family
|
12
|
+
end
|
13
|
+
|
14
|
+
def column_family
|
15
|
+
@column_family || name.pluralize
|
16
|
+
end
|
17
|
+
|
18
|
+
def base_class
|
19
|
+
klass = self
|
20
|
+
while klass.superclass != Base
|
21
|
+
klass = klass.superclass
|
22
|
+
end
|
23
|
+
klass
|
24
|
+
end
|
25
|
+
|
26
|
+
delegate :compute_type, :to => 'ActiveRecord::Base'
|
27
|
+
end
|
28
|
+
|
29
|
+
extend ActiveModel::Naming
|
30
|
+
include ActiveModel::Conversion
|
31
|
+
extend ActiveSupport::DescendantsTracker
|
32
|
+
|
33
|
+
include Connection
|
34
|
+
include Consistency
|
35
|
+
include Identity
|
36
|
+
include Attributes
|
37
|
+
include Persistence
|
38
|
+
include Callbacks
|
39
|
+
include Dirty
|
40
|
+
include Validations
|
41
|
+
include Associations
|
42
|
+
include Batches
|
43
|
+
include FinderMethods
|
44
|
+
include Timestamps
|
45
|
+
|
46
|
+
attr_accessor :key
|
47
|
+
|
48
|
+
include Serialization
|
49
|
+
include Migrations
|
50
|
+
include Mocking
|
51
|
+
|
52
|
+
def initialize(attributes={})
|
53
|
+
@key = attributes.delete(:key)
|
54
|
+
@new_record = true
|
55
|
+
@destroyed = false
|
56
|
+
@attributes = {}.with_indifferent_access
|
57
|
+
self.attributes = attributes
|
58
|
+
@schema_version = self.class.current_schema_version
|
59
|
+
end
|
60
|
+
|
61
|
+
def attributes
|
62
|
+
@attributes.merge(:id => id)
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_param
|
66
|
+
id.to_s if persisted?
|
67
|
+
end
|
68
|
+
|
69
|
+
def hash
|
70
|
+
id.hash
|
71
|
+
end
|
72
|
+
|
73
|
+
def ==(comparison_object)
|
74
|
+
comparison_object.equal?(self) ||
|
75
|
+
(comparison_object.instance_of?(self.class) &&
|
76
|
+
comparison_object.key == key &&
|
77
|
+
!comparison_object.new_record?)
|
78
|
+
end
|
79
|
+
|
80
|
+
def eql?(comparison_object)
|
81
|
+
self == (comparison_object)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
|
85
|
+
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
|
86
|
+
# (Alias for the protected read_attribute method).
|
87
|
+
def [](attr_name)
|
88
|
+
read_attribute(attr_name)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
92
|
+
# (Alias for the protected write_attribute method).
|
93
|
+
def []=(attr_name, value)
|
94
|
+
write_attribute(attr_name, value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Batches
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def find_each
|
7
|
+
connection.each(column_family) do |k, v|
|
8
|
+
yield instantiate(k, v)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_in_batches(options = {})
|
13
|
+
batch_size = options.delete(:batch_size) || 1000
|
14
|
+
|
15
|
+
batch = []
|
16
|
+
|
17
|
+
find_each do |record|
|
18
|
+
batch << record
|
19
|
+
if batch.size == batch_size
|
20
|
+
yield(batch)
|
21
|
+
batch = []
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
if batch.size > 0
|
26
|
+
yield batch
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Callbacks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
extend ActiveModel::Callbacks
|
7
|
+
define_model_callbacks :save, :create, :destroy, :update
|
8
|
+
end
|
9
|
+
|
10
|
+
def destroy #:nodoc:
|
11
|
+
_run_destroy_callbacks { super }
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def create_or_update #:nodoc:
|
16
|
+
_run_save_callbacks { super }
|
17
|
+
end
|
18
|
+
|
19
|
+
def create #:nodoc:
|
20
|
+
_run_create_callbacks { super }
|
21
|
+
end
|
22
|
+
|
23
|
+
def update(*) #:nodoc:
|
24
|
+
_run_update_callbacks { super }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Connection
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :connection_spec
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
DEFAULT_OPTIONS = {
|
11
|
+
servers: "127.0.0.1:9160",
|
12
|
+
thrift: {}
|
13
|
+
}
|
14
|
+
def establish_connection(spec)
|
15
|
+
spec.reverse_merge!(DEFAULT_OPTIONS)
|
16
|
+
@connection = Cassandra.new(spec[:keyspace], spec[:servers], spec[:thrift].symbolize_keys!)
|
17
|
+
end
|
18
|
+
|
19
|
+
def connection=(val)
|
20
|
+
@connection = val
|
21
|
+
end
|
22
|
+
|
23
|
+
def connection
|
24
|
+
establish_connection(connection_spec) if @connection.nil?
|
25
|
+
@connection
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Consistency
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
cattr_accessor :consistency_levels
|
7
|
+
self.consistency_levels = [:one, :quorum, :all]
|
8
|
+
|
9
|
+
class_attribute :write_consistency
|
10
|
+
class_attribute :read_consistency
|
11
|
+
self.write_consistency = :quorum
|
12
|
+
self.read_consistency = :quorum
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
THRIFT_LEVELS = {
|
17
|
+
:one => Cassandra::Consistency::ONE,
|
18
|
+
:quorum => Cassandra::Consistency::QUORUM,
|
19
|
+
:all => Cassandra::Consistency::ALL
|
20
|
+
}
|
21
|
+
|
22
|
+
def thrift_read_consistency
|
23
|
+
THRIFT_LEVELS[read_consistency] || (raise "Invalid consistency level #{read_consistency}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def thrift_write_consistency
|
27
|
+
THRIFT_LEVELS[write_consistency] || (raise "Invalid consistency level #{write_consistency}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|