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
@@ -0,0 +1,89 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
|
3
|
+
module Tasks
|
4
|
+
|
5
|
+
class Keyspace
|
6
|
+
|
7
|
+
def self.parse(hash)
|
8
|
+
ks = Cassandra::Keyspace.new.with_fields hash
|
9
|
+
ks.cf_defs = []
|
10
|
+
hash['cf_defs'].each do |cf|
|
11
|
+
ks.cf_defs << Cassandra::ColumnFamily.new.with_fields(cf)
|
12
|
+
end
|
13
|
+
ks
|
14
|
+
end
|
15
|
+
|
16
|
+
def exists?(name)
|
17
|
+
connection.keyspaces.include? name.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def create(name, options = {})
|
21
|
+
opts = { :name => name.to_s,
|
22
|
+
:strategy_class => 'org.apache.cassandra.locator.LocalStrategy',
|
23
|
+
:replication_factor => 1,
|
24
|
+
:cf_defs => [] }.merge(options)
|
25
|
+
|
26
|
+
ks = Cassandra::Keyspace.new.with_fields(opts)
|
27
|
+
connection.add_keyspace ks
|
28
|
+
end
|
29
|
+
|
30
|
+
def drop(name)
|
31
|
+
connection.drop_keyspace name.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def set(name)
|
35
|
+
connection.keyspace = name.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def get
|
39
|
+
connection.keyspace
|
40
|
+
end
|
41
|
+
|
42
|
+
def clear
|
43
|
+
return puts 'Cannot clear system keyspace' if connection.keyspace == 'system'
|
44
|
+
|
45
|
+
connection.clear_keyspace!
|
46
|
+
end
|
47
|
+
|
48
|
+
def schema_dump
|
49
|
+
connection.schema
|
50
|
+
end
|
51
|
+
|
52
|
+
def schema_load(schema)
|
53
|
+
connection.schema.cf_defs.each do |cf|
|
54
|
+
connection.drop_column_family cf.name
|
55
|
+
end
|
56
|
+
|
57
|
+
keyspace = get
|
58
|
+
schema.cf_defs.each do |cf|
|
59
|
+
cf.keyspace = keyspace
|
60
|
+
connection.add_column_family cf
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def connection
|
67
|
+
unless @connection
|
68
|
+
c = CassandraObject::Base.connection
|
69
|
+
@connection = Cassandra.new('system', c.servers, c.thrift_client_options)
|
70
|
+
end
|
71
|
+
@connection
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
class Cassandra
|
81
|
+
class Keyspace
|
82
|
+
def with_fields(options)
|
83
|
+
struct_fields.collect { |f| f[1][:name] }.each do |f|
|
84
|
+
send("#{f}=", options[f.to_sym] || options[f.to_s])
|
85
|
+
end
|
86
|
+
self
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
namespace :ks do
|
2
|
+
task :configure => :environment do
|
3
|
+
@configs = YAML.load_file(Rails.root.join("config", "cassandra.yml"))
|
4
|
+
@config = @configs[Rails.env || 'development']
|
5
|
+
end
|
6
|
+
|
7
|
+
#task :set_keyspace => :configure do
|
8
|
+
#set_keyspace
|
9
|
+
#end
|
10
|
+
|
11
|
+
desc 'Create the keyspace in config/cassandra.yml for the current environment'
|
12
|
+
task :create => :configure do
|
13
|
+
CassandraObject::Tasks::Keyspace.new.create @config['keyspace'], @config
|
14
|
+
puts "Created keyspace: #{@config['keyspace']}"
|
15
|
+
end
|
16
|
+
|
17
|
+
namespace :create do
|
18
|
+
desc 'Create keyspaces in config/cassandra.yml for all environments'
|
19
|
+
task :all => :configure do
|
20
|
+
created = []
|
21
|
+
@configs.values.each do |config|
|
22
|
+
CassandraObject::Tasks::Keyspace.new.create config['keyspace'], config
|
23
|
+
created << config['keyspace']
|
24
|
+
end
|
25
|
+
puts "Created keyspaces: #{created.join(', ')}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'Drop keyspace in config/cassandra.yml for the current environment'
|
30
|
+
task :drop => :configure do
|
31
|
+
CassandraObject::Tasks::Keyspace.new.drop @config['keyspace']
|
32
|
+
puts "Dropped keyspace: #{@config['keyspace']}"
|
33
|
+
end
|
34
|
+
|
35
|
+
namespace :drop do
|
36
|
+
desc 'Drop keyspaces in config/cassandra.yml for all environments'
|
37
|
+
task :all => :configure do
|
38
|
+
dropped = []
|
39
|
+
@configs.values.each do |config|
|
40
|
+
CassandraObject::Tasks::Keyspace.new.drop config['keyspace']
|
41
|
+
dropped << config['keyspace']
|
42
|
+
end
|
43
|
+
puts "Dropped keyspaces: #{dropped.join(', ')}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'Migrate the keyspace (options: VERSION=x)'
|
48
|
+
task :migrate => :configure do
|
49
|
+
version = ( ENV['VERSION'] ? ENV['VERSION'].to_i : nil )
|
50
|
+
CassandraObject::Schema::Migrator.migrate CassandraObject::Schema::Migrator.migrations_path, version
|
51
|
+
schema_dump
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n)'
|
55
|
+
task :rollback => :set_keyspace do
|
56
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
57
|
+
CassandraObject::Schema::Migrator.rollback CassandraObject::Schema::Migrator.migrations_path, step
|
58
|
+
schema_dump
|
59
|
+
end
|
60
|
+
|
61
|
+
desc 'Pushes the schema to the next version (specify steps w/ STEP=n)'
|
62
|
+
task :forward => :set_keyspace do
|
63
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
64
|
+
CassandraObject::Schema::Migrator.forward CassandraObject::Schema::Migrator.migrations_path, step
|
65
|
+
schema_dump
|
66
|
+
end
|
67
|
+
|
68
|
+
namespace :schema do
|
69
|
+
desc 'Create ks/schema.json file that can be portably used against any Cassandra instance supported by CassandraObject'
|
70
|
+
task :dump => :configure do
|
71
|
+
schema_dump
|
72
|
+
end
|
73
|
+
|
74
|
+
desc 'Load ks/schema.json file into Cassandra'
|
75
|
+
task :load => :configure do
|
76
|
+
schema_load
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
namespace :test do
|
81
|
+
desc 'Load the development schema in to the test keyspace'
|
82
|
+
task :prepare => :configure do
|
83
|
+
schema_dump :development
|
84
|
+
schema_load :test
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'Retrieves the current schema version number'
|
89
|
+
task :version => :set_keyspace do
|
90
|
+
version = CassandraObject::Schema::Migrator.current_version
|
91
|
+
puts "Current version: #{version}"
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def schema_dump(env = Rails.env)
|
97
|
+
ks = set_keyspace env
|
98
|
+
File.open "#{Rails.root}/ks/schema.json", 'w' do |file|
|
99
|
+
schema = ActiveSupport::JSON.decode(ks.schema_dump.to_json)
|
100
|
+
JSON.pretty_generate(schema).split(/\n/).each do |line|
|
101
|
+
file.puts line
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def schema_load(env = Rails.env)
|
107
|
+
ks = set_keyspace env
|
108
|
+
File.open "#{Rails.root}/ks/schema.json", 'r' do |file|
|
109
|
+
hash = JSON.parse(file.read(nil))
|
110
|
+
ks.schema_load CassandraObject::Tasks::Keyspace.parse(hash)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_keyspace(env = Rails.env)
|
115
|
+
config = @configs[env.to_s || 'development']
|
116
|
+
ks = CassandraObject::Tasks::Keyspace.new
|
117
|
+
ks.set config['keyspace']
|
118
|
+
ks
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Timestamps
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
attribute :created_at, type: :time#_with_zone
|
7
|
+
attribute :updated_at, type: :time#_with_zone
|
8
|
+
|
9
|
+
before_create do #|r|
|
10
|
+
self.created_at ||= Time.current
|
11
|
+
self.updated_at ||= Time.current
|
12
|
+
end
|
13
|
+
|
14
|
+
before_update if: :changed? do #|r|
|
15
|
+
self.updated_at = Time.current
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class Type
|
3
|
+
class TypeMapping < Struct.new(:expected_type, :converter)
|
4
|
+
end
|
5
|
+
|
6
|
+
cattr_accessor :attribute_types
|
7
|
+
self.attribute_types = {}.with_indifferent_access
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def register(name, expected_type, converter)
|
11
|
+
attribute_types[name] = TypeMapping.new(expected_type, converter)
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_mapping(name)
|
15
|
+
attribute_types[name]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module ArrayType
|
4
|
+
def encode(array)
|
5
|
+
raise ArgumentError.new("#{self} requires an Array") unless array.kind_of?(Array)
|
6
|
+
array.to_json
|
7
|
+
end
|
8
|
+
module_function :encode
|
9
|
+
|
10
|
+
def decode(str)
|
11
|
+
ActiveSupport::JSON.decode(str)
|
12
|
+
end
|
13
|
+
module_function :decode
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module BooleanType
|
4
|
+
TRUE_VALS = [true, 'true', '1']
|
5
|
+
FALSE_VALS = [false, 'false', '0', '', nil]
|
6
|
+
VALID_VALS = TRUE_VALS + FALSE_VALS
|
7
|
+
|
8
|
+
def encode(bool)
|
9
|
+
unless VALID_VALS.include?(bool)
|
10
|
+
raise ArgumentError.new("#{self} requires a boolean")
|
11
|
+
end
|
12
|
+
TRUE_VALS.include?(bool) ? '1' : '0'
|
13
|
+
end
|
14
|
+
module_function :encode
|
15
|
+
|
16
|
+
def decode(str)
|
17
|
+
raise ArgumentError.new("Cannot convert #{str} into a boolean") unless VALID_VALS.include?(str)
|
18
|
+
TRUE_VALS.include?(str)
|
19
|
+
end
|
20
|
+
module_function :decode
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module DateType
|
4
|
+
FORMAT = '%Y-%m-%d'
|
5
|
+
REGEX = /\A\d{4}-\d{2}-\d{2}\Z/
|
6
|
+
def encode(date)
|
7
|
+
raise ArgumentError.new("#{self} requires a Date") unless date.kind_of?(Date)
|
8
|
+
date.strftime(FORMAT)
|
9
|
+
end
|
10
|
+
module_function :encode
|
11
|
+
|
12
|
+
def decode(str)
|
13
|
+
return nil if str.empty?
|
14
|
+
raise ArgumentError.new("Cannot convert #{str} into a Date") unless str.kind_of?(String) && str.match(REGEX)
|
15
|
+
Date.strptime(str, FORMAT)
|
16
|
+
end
|
17
|
+
module_function :decode
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module FloatType
|
4
|
+
REGEX = /\A[-+]?\d+(\.\d+)?\Z/
|
5
|
+
def encode(float)
|
6
|
+
raise ArgumentError.new("#{self} requires a Float") unless float.kind_of?(Float)
|
7
|
+
float.to_s
|
8
|
+
end
|
9
|
+
module_function :encode
|
10
|
+
|
11
|
+
def decode(str)
|
12
|
+
return nil if str.empty?
|
13
|
+
raise ArgumentError.new("Cannot convert #{str} into a Float") unless str.kind_of?(String) && str.match(REGEX)
|
14
|
+
str.to_f
|
15
|
+
end
|
16
|
+
module_function :decode
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module HashType
|
4
|
+
def encode(hash)
|
5
|
+
raise ArgumentError.new("#{self} requires a Hash") unless hash.kind_of?(Hash)
|
6
|
+
ActiveSupport::JSON.encode(hash)
|
7
|
+
end
|
8
|
+
module_function :encode
|
9
|
+
|
10
|
+
def decode(str)
|
11
|
+
ActiveSupport::JSON.decode(str)
|
12
|
+
end
|
13
|
+
module_function :decode
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module IntegerType
|
4
|
+
REGEX = /\A[-+]?\d+\Z/
|
5
|
+
def encode(int)
|
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("Cannot convert #{str} into an Integer") unless str.kind_of?(String) && str.match(REGEX)
|
14
|
+
str.to_i
|
15
|
+
end
|
16
|
+
module_function :decode
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module SetType
|
4
|
+
def encode(set)
|
5
|
+
if set.kind_of?(Set)
|
6
|
+
set.to_json
|
7
|
+
elsif set.kind_of?(Array)
|
8
|
+
set.uniq.to_json
|
9
|
+
else
|
10
|
+
raise ArgumentError.new("#{self} requires an Array or Set")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
module_function :encode
|
14
|
+
|
15
|
+
def decode(str)
|
16
|
+
return str.to_a if str.kind_of?(Set)
|
17
|
+
ActiveSupport::JSON.decode(str)
|
18
|
+
end
|
19
|
+
module_function :decode
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module StringType
|
4
|
+
def encode(str)
|
5
|
+
raise ArgumentError.new("#{self} requires a String") unless str.kind_of?(String)
|
6
|
+
str.dup
|
7
|
+
end
|
8
|
+
module_function :encode
|
9
|
+
|
10
|
+
def decode(str)
|
11
|
+
str
|
12
|
+
end
|
13
|
+
module_function :decode
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module TimeType
|
4
|
+
# lifted from the implementation of Time.xmlschema and simplified
|
5
|
+
REGEX = /\A\s*
|
6
|
+
(-?\d+)-(\d\d)-(\d\d)
|
7
|
+
T
|
8
|
+
(\d\d):(\d\d):(\d\d)
|
9
|
+
(\.\d*)?
|
10
|
+
(Z|[+-]\d\d:\d\d)?
|
11
|
+
\s*\z/ix
|
12
|
+
|
13
|
+
def encode(time)
|
14
|
+
raise ArgumentError.new("#{self} requires a Time") unless time.kind_of?(Time)
|
15
|
+
time.xmlschema(6)
|
16
|
+
end
|
17
|
+
module_function :encode
|
18
|
+
|
19
|
+
def decode(str)
|
20
|
+
return nil if str.empty?
|
21
|
+
raise ArgumentError.new("Cannot convert #{str} into a Time") unless str.kind_of?(String) && str.match(REGEX)
|
22
|
+
Time.xmlschema(str)
|
23
|
+
end
|
24
|
+
module_function :decode
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module TimeWithZoneType
|
4
|
+
def encode(time)
|
5
|
+
raise ArgumentError.new("#{self} requires a Time") unless time.kind_of?(Time)
|
6
|
+
time.utc.xmlschema(6)
|
7
|
+
end
|
8
|
+
module_function :encode
|
9
|
+
|
10
|
+
def decode(str)
|
11
|
+
return nil if str.empty?
|
12
|
+
raise ArgumentError.new("Cannot convert #{str} into a Time") unless str.kind_of?(String) && str.match(TimeType::REGEX)
|
13
|
+
Time.xmlschema(str).in_time_zone
|
14
|
+
end
|
15
|
+
module_function :decode
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
module UTF8StringType
|
4
|
+
def encode(str)
|
5
|
+
# This is technically the most correct, but it is a pain to require utf-8 encoding for all strings. Should revisit.
|
6
|
+
#raise ArgumentError.new("#{self} requires a UTF-8 encoded String") unless str.kind_of?(String) && str.encoding == Encoding::UTF_8
|
7
|
+
raise ArgumentError.new("#{self} requires a String") unless str.kind_of?(String)
|
8
|
+
str.dup
|
9
|
+
end
|
10
|
+
module_function :encode
|
11
|
+
|
12
|
+
def decode(str)
|
13
|
+
str.force_encoding('UTF-8')
|
14
|
+
end
|
15
|
+
module_function :decode
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
CassandraObject::Type.register(:array, Array, CassandraObject::Types::ArrayType)
|
2
|
+
CassandraObject::Type.register(:boolean, Object, CassandraObject::Types::BooleanType)
|
3
|
+
CassandraObject::Type.register(:date, Date, CassandraObject::Types::DateType)
|
4
|
+
CassandraObject::Type.register(:float, Float, CassandraObject::Types::FloatType)
|
5
|
+
CassandraObject::Type.register(:hash, Hash, CassandraObject::Types::HashType)
|
6
|
+
CassandraObject::Type.register(:integer, Integer, CassandraObject::Types::IntegerType)
|
7
|
+
CassandraObject::Type.register(:set, Array, CassandraObject::Types::SetType)
|
8
|
+
CassandraObject::Type.register(:time, Time, CassandraObject::Types::TimeType)
|
9
|
+
CassandraObject::Type.register(:time_with_zone, ActiveSupport::TimeWithZone, CassandraObject::Types::TimeWithZoneType)
|
10
|
+
CassandraObject::Type.register(:string, String, CassandraObject::Types::UTF8StringType) #This could be changed to StringType to support non-utf8 strings
|
11
|
+
CassandraObject::Type.register(:utf8, String, CassandraObject::Types::UTF8StringType)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
class RecordInvalid < StandardError
|
3
|
+
attr_reader :record
|
4
|
+
def initialize(record)
|
5
|
+
@record = record
|
6
|
+
super("Invalid record: #{@record.errors.full_messages.to_sentence}")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Validations
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
include ActiveModel::Validations
|
13
|
+
|
14
|
+
included do
|
15
|
+
define_model_callbacks :validation
|
16
|
+
define_callbacks :validate, :scope => :name
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def create!(attributes = {})
|
21
|
+
new(attributes).tap do |object|
|
22
|
+
object.save!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
run_callbacks :validation do
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def save(options={})
|
34
|
+
perform_validations(options) ? super : false
|
35
|
+
end
|
36
|
+
|
37
|
+
def save!
|
38
|
+
save || raise(RecordInvalid.new(self))
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def perform_validations(options={})
|
43
|
+
(options[:validate] != false) ? valid? : true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rails/all'
|
2
|
+
|
3
|
+
module CassandraObject
|
4
|
+
extend ActiveSupport::Autoload
|
5
|
+
|
6
|
+
autoload :Base
|
7
|
+
autoload :Connection
|
8
|
+
autoload :Attributes
|
9
|
+
autoload :Dirty
|
10
|
+
autoload :Consistency
|
11
|
+
autoload :Persistence
|
12
|
+
autoload :Callbacks
|
13
|
+
autoload :Validations
|
14
|
+
autoload :Identity
|
15
|
+
autoload :Serialization
|
16
|
+
autoload :Associations
|
17
|
+
autoload :Migrations
|
18
|
+
autoload :Cursor
|
19
|
+
autoload :Collection
|
20
|
+
autoload :Mocking
|
21
|
+
autoload :Batches
|
22
|
+
autoload :FinderMethods
|
23
|
+
autoload :Timestamps
|
24
|
+
autoload :Type
|
25
|
+
autoload :Schema
|
26
|
+
|
27
|
+
module Tasks
|
28
|
+
extend ActiveSupport::Autoload
|
29
|
+
autoload :Keyspace
|
30
|
+
autoload :ColumnFamily
|
31
|
+
end
|
32
|
+
|
33
|
+
module Types
|
34
|
+
extend ActiveSupport::Autoload
|
35
|
+
|
36
|
+
autoload :ArrayType
|
37
|
+
autoload :BooleanType
|
38
|
+
autoload :DateType
|
39
|
+
autoload :FloatType
|
40
|
+
autoload :HashType
|
41
|
+
autoload :IntegerType
|
42
|
+
autoload :SetType
|
43
|
+
autoload :TimeType
|
44
|
+
autoload :TimeWithZoneType
|
45
|
+
autoload :UTF8StringType
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
require 'cassandra_object/railtie'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'sessionm-cassandra_object'
|
5
|
+
s.version = '2.2.6'
|
6
|
+
s.description = 'Cassandra ActiveModel'
|
7
|
+
s.summary = 'Cassandra ActiveModel'
|
8
|
+
|
9
|
+
s.required_ruby_version = '>= 1.9.2'
|
10
|
+
s.required_rubygems_version = '>= 1.3.5'
|
11
|
+
|
12
|
+
s.authors = ["Michael Koziarski", "gotime", "sessionm"]
|
13
|
+
s.email = 'klange@sessionm.com'
|
14
|
+
s.homepage = 'http://github.com/sessionm/cassandra_object'
|
15
|
+
|
16
|
+
s.extra_rdoc_files = ["README.markdown"]
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
|
21
|
+
s.add_runtime_dependency('rails', "~> 3.0")
|
22
|
+
s.add_runtime_dependency('cassandra', "~> 0.11.3")
|
23
|
+
|
24
|
+
s.add_development_dependency('bundler', "~> 1.0.0")
|
25
|
+
end
|
26
|
+
|
data/test/base_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CassandraObject::BaseTest < CassandraObject::TestCase
|
4
|
+
class Son < CassandraObject::Base
|
5
|
+
end
|
6
|
+
|
7
|
+
class Grandson < Son
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'base_class' do
|
11
|
+
assert_equal Son, Son.base_class
|
12
|
+
assert_equal Son, Grandson.base_class
|
13
|
+
end
|
14
|
+
|
15
|
+
test 'column family' do
|
16
|
+
assert_equal 'CassandraObject::BaseTest::Sons', Son.column_family
|
17
|
+
end
|
18
|
+
|
19
|
+
test 'to_param' do
|
20
|
+
issue = Issue.create
|
21
|
+
assert_equal issue.id, issue.to_param
|
22
|
+
end
|
23
|
+
|
24
|
+
test 'hash' do
|
25
|
+
issue = Issue.create
|
26
|
+
assert_equal issue.id.hash, issue.hash
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CassandraObject::BatchesTest < CassandraObject::TestCase
|
4
|
+
test 'find_each' do
|
5
|
+
Issue.create
|
6
|
+
Issue.create
|
7
|
+
|
8
|
+
issues = []
|
9
|
+
Issue.find_each do |issue|
|
10
|
+
issues << issue
|
11
|
+
end
|
12
|
+
|
13
|
+
assert_equal Issue.all.to_set, issues.to_set
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'find_in_batches' do
|
17
|
+
Issue.create
|
18
|
+
Issue.create
|
19
|
+
Issue.create
|
20
|
+
|
21
|
+
issue_batches = []
|
22
|
+
Issue.find_in_batches(batch_size: 2) do |issues|
|
23
|
+
issue_batches << issues
|
24
|
+
end
|
25
|
+
|
26
|
+
assert_equal 2, issue_batches.size
|
27
|
+
assert issue_batches.any? { |issues| issues.size == 2 }
|
28
|
+
assert issue_batches.any? { |issues| issues.size == 1 }
|
29
|
+
end
|
30
|
+
end
|