sessionm-cassandra_object 2.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|