cassandra_object_rails 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +3 -0
- data/.travis.yml +7 -0
- data/CHANGELOG +5 -0
- data/Gemfile +8 -0
- data/LICENSE +13 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +97 -0
- data/Rakefile +12 -0
- data/cassandra_object_rails.gemspec +26 -0
- data/lib/cassandra_object/attribute_methods.rb +87 -0
- data/lib/cassandra_object/attribute_methods/definition.rb +19 -0
- data/lib/cassandra_object/attribute_methods/dirty.rb +44 -0
- data/lib/cassandra_object/attribute_methods/primary_key.rb +25 -0
- data/lib/cassandra_object/attribute_methods/typecasting.rb +59 -0
- data/lib/cassandra_object/base.rb +69 -0
- data/lib/cassandra_object/belongs_to.rb +63 -0
- data/lib/cassandra_object/belongs_to/association.rb +48 -0
- data/lib/cassandra_object/belongs_to/builder.rb +40 -0
- data/lib/cassandra_object/belongs_to/reflection.rb +30 -0
- data/lib/cassandra_object/callbacks.rb +29 -0
- data/lib/cassandra_object/config.rb +15 -0
- data/lib/cassandra_object/connection.rb +36 -0
- data/lib/cassandra_object/consistency.rb +18 -0
- data/lib/cassandra_object/core.rb +59 -0
- data/lib/cassandra_object/errors.rb +6 -0
- data/lib/cassandra_object/identity.rb +24 -0
- data/lib/cassandra_object/inspect.rb +25 -0
- data/lib/cassandra_object/log_subscriber.rb +29 -0
- data/lib/cassandra_object/persistence.rb +169 -0
- data/lib/cassandra_object/rails_initializer.rb +19 -0
- data/lib/cassandra_object/railtie.rb +11 -0
- data/lib/cassandra_object/savepoints.rb +79 -0
- data/lib/cassandra_object/schema.rb +78 -0
- data/lib/cassandra_object/schema/tasks.rb +48 -0
- data/lib/cassandra_object/scope.rb +48 -0
- data/lib/cassandra_object/scope/batches.rb +32 -0
- data/lib/cassandra_object/scope/finder_methods.rb +47 -0
- data/lib/cassandra_object/scope/query_methods.rb +111 -0
- data/lib/cassandra_object/scoping.rb +19 -0
- data/lib/cassandra_object/serialization.rb +6 -0
- data/lib/cassandra_object/tasks/cassandra.rake +53 -0
- data/lib/cassandra_object/timestamps.rb +19 -0
- data/lib/cassandra_object/type.rb +16 -0
- data/lib/cassandra_object/types.rb +8 -0
- data/lib/cassandra_object/types/array_type.rb +76 -0
- data/lib/cassandra_object/types/base_type.rb +26 -0
- data/lib/cassandra_object/types/boolean_type.rb +20 -0
- data/lib/cassandra_object/types/date_type.rb +17 -0
- data/lib/cassandra_object/types/float_type.rb +16 -0
- data/lib/cassandra_object/types/integer_type.rb +16 -0
- data/lib/cassandra_object/types/json_type.rb +52 -0
- data/lib/cassandra_object/types/string_type.rb +15 -0
- data/lib/cassandra_object/types/time_type.rb +16 -0
- data/lib/cassandra_object/validations.rb +44 -0
- data/lib/cassandra_object_rails.rb +64 -0
- data/test/support/connect.rb +17 -0
- data/test/support/issue.rb +5 -0
- data/test/support/teardown.rb +24 -0
- data/test/test_helper.rb +34 -0
- data/test/unit/active_model_test.rb +18 -0
- data/test/unit/attribute_methods/definition_test.rb +13 -0
- data/test/unit/attribute_methods/dirty_test.rb +71 -0
- data/test/unit/attribute_methods/primary_key_test.rb +26 -0
- data/test/unit/attribute_methods/typecasting_test.rb +112 -0
- data/test/unit/attribute_methods_test.rb +39 -0
- data/test/unit/base_test.rb +20 -0
- data/test/unit/belongs_to/reflection_test.rb +12 -0
- data/test/unit/belongs_to_test.rb +62 -0
- data/test/unit/callbacks_test.rb +46 -0
- data/test/unit/config_test.rb +23 -0
- data/test/unit/connection_test.rb +10 -0
- data/test/unit/consistency_test.rb +13 -0
- data/test/unit/core_test.rb +55 -0
- data/test/unit/identity_test.rb +26 -0
- data/test/unit/inspect_test.rb +26 -0
- data/test/unit/log_subscriber_test.rb +22 -0
- data/test/unit/persistence_test.rb +187 -0
- data/test/unit/savepoints_test.rb +35 -0
- data/test/unit/schema/tasks_test.rb +29 -0
- data/test/unit/schema_test.rb +47 -0
- data/test/unit/scope/batches_test.rb +30 -0
- data/test/unit/scope/finder_methods_test.rb +51 -0
- data/test/unit/scope/query_methods_test.rb +26 -0
- data/test/unit/scoping_test.rb +7 -0
- data/test/unit/timestamps_test.rb +27 -0
- data/test/unit/types/array_type_test.rb +71 -0
- data/test/unit/types/base_type_test.rb +24 -0
- data/test/unit/types/boolean_type_test.rb +24 -0
- data/test/unit/types/date_type_test.rb +11 -0
- data/test/unit/types/float_type_test.rb +17 -0
- data/test/unit/types/integer_type_test.rb +19 -0
- data/test/unit/types/json_type_test.rb +77 -0
- data/test/unit/types/string_type_test.rb +32 -0
- data/test/unit/types/time_type_test.rb +14 -0
- data/test/unit/validations_test.rb +27 -0
- metadata +208 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
class BaseType
|
4
|
+
attr_accessor :options
|
5
|
+
def initialize(options = {})
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def default
|
10
|
+
options[:default].duplicable? ? options[:default].dup : options[:default]
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode(value)
|
14
|
+
value.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def decode(str)
|
18
|
+
str
|
19
|
+
end
|
20
|
+
|
21
|
+
def wrap(record, name, value)
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
class BooleanType < BaseType
|
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("#{bool.inspect} is not a Boolean")
|
11
|
+
end
|
12
|
+
TRUE_VALS.include?(bool) ? '1' : '0'
|
13
|
+
end
|
14
|
+
|
15
|
+
def decode(str)
|
16
|
+
TRUE_VALS.include?(str)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
class DateType < BaseType
|
4
|
+
FORMAT = '%Y-%m-%d'
|
5
|
+
REGEX = /\A\d{4}-\d{2}-\d{2}\Z/
|
6
|
+
|
7
|
+
def encode(value)
|
8
|
+
raise ArgumentError.new("#{value.inspect} is not a Date") unless value.kind_of?(Date)
|
9
|
+
value.strftime(FORMAT)
|
10
|
+
end
|
11
|
+
|
12
|
+
def decode(str)
|
13
|
+
Date.parse(str)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
class FloatType < BaseType
|
4
|
+
REGEX = /\A[-+]?\d+(\.\d+)?\Z/
|
5
|
+
def encode(float)
|
6
|
+
raise ArgumentError.new("#{float.inspect} is not a Float") unless float.kind_of?(Float)
|
7
|
+
float.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def decode(str)
|
11
|
+
return nil if str.empty?
|
12
|
+
str.to_f
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
class IntegerType < BaseType
|
4
|
+
REGEX = /\A[-+]?\d+\Z/
|
5
|
+
def encode(int)
|
6
|
+
raise ArgumentError.new("#{int.inspect} is not an Integer.") unless int.kind_of?(Integer)
|
7
|
+
int.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def decode(str)
|
11
|
+
return nil if str.empty?
|
12
|
+
str.to_i
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
class JsonType < BaseType
|
4
|
+
class DirtyHash < Hash
|
5
|
+
attr_accessor :record, :name, :options
|
6
|
+
def initialize(record, name, hash, options)
|
7
|
+
@record = record
|
8
|
+
@name = name.to_s
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
self.merge!(hash)
|
12
|
+
@init_hash = self.hash
|
13
|
+
@init_value = hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(obj, val)
|
17
|
+
modifying do super end
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete(obj)
|
21
|
+
modifying do super end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def modifying
|
26
|
+
result = yield
|
27
|
+
|
28
|
+
if !record.changed_attributes.key?(name) && @init_hash != self.hash
|
29
|
+
record.changed_attributes[name] = @init_value
|
30
|
+
end
|
31
|
+
|
32
|
+
record.send("#{name}=", self)
|
33
|
+
|
34
|
+
result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def encode(hash)
|
39
|
+
ActiveSupport::JSON.encode(hash)
|
40
|
+
end
|
41
|
+
|
42
|
+
def decode(str)
|
43
|
+
ActiveSupport::JSON.decode(str)
|
44
|
+
end
|
45
|
+
|
46
|
+
def wrap(record, name, value)
|
47
|
+
DirtyHash.new(record, name, Hash[value], options)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
class StringType < BaseType
|
4
|
+
def encode(str)
|
5
|
+
raise ArgumentError.new("#{str.inspect} is not a String") unless str.kind_of?(String)
|
6
|
+
str.dup
|
7
|
+
end
|
8
|
+
|
9
|
+
def wrap(record, name, value)
|
10
|
+
value = value.to_s
|
11
|
+
(value.frozen? ? value.dup : value).force_encoding('UTF-8')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CassandraObject
|
2
|
+
module Types
|
3
|
+
class TimeType < BaseType
|
4
|
+
def encode(time)
|
5
|
+
raise ArgumentError.new("#{time.inspect} is not a Time") unless time.kind_of?(Time)
|
6
|
+
time.utc.xmlschema(6)
|
7
|
+
end
|
8
|
+
|
9
|
+
def decode(str)
|
10
|
+
Time.parse(str).utc if str
|
11
|
+
rescue
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,44 @@
|
|
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_callbacks :validate, scope: :name
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def create!(attributes = {})
|
20
|
+
new(attributes).tap do |object|
|
21
|
+
object.save!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(options={})
|
27
|
+
if perform_validations(options)
|
28
|
+
super
|
29
|
+
true
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def save!
|
36
|
+
save || raise(RecordInvalid.new(self))
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
def perform_validations(options={})
|
41
|
+
(options[:validate] != false) ? valid? : true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'active_model'
|
3
|
+
require 'cassandra-cql'
|
4
|
+
require 'cassandra_object/errors'
|
5
|
+
|
6
|
+
module CassandraObject
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
|
9
|
+
autoload :AttributeMethods
|
10
|
+
autoload :Base
|
11
|
+
autoload :BelongsTo
|
12
|
+
autoload :Callbacks
|
13
|
+
autoload :Config
|
14
|
+
autoload :Connection
|
15
|
+
autoload :Consistency
|
16
|
+
autoload :Core
|
17
|
+
autoload :Identity
|
18
|
+
autoload :Inspect
|
19
|
+
autoload :Persistence
|
20
|
+
autoload :Savepoints
|
21
|
+
autoload :Schema
|
22
|
+
autoload :Scope
|
23
|
+
autoload :Scoping
|
24
|
+
autoload :Serialization
|
25
|
+
autoload :Timestamps
|
26
|
+
autoload :Type
|
27
|
+
autoload :Validations
|
28
|
+
|
29
|
+
module BelongsTo
|
30
|
+
extend ActiveSupport::Autoload
|
31
|
+
|
32
|
+
autoload :Association
|
33
|
+
autoload :Builder
|
34
|
+
autoload :Reflection
|
35
|
+
end
|
36
|
+
|
37
|
+
module AttributeMethods
|
38
|
+
extend ActiveSupport::Autoload
|
39
|
+
|
40
|
+
eager_autoload do
|
41
|
+
autoload :Definition
|
42
|
+
autoload :Dirty
|
43
|
+
autoload :PrimaryKey
|
44
|
+
autoload :Typecasting
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module Types
|
49
|
+
extend ActiveSupport::Autoload
|
50
|
+
|
51
|
+
autoload :BaseType
|
52
|
+
autoload :ArrayType
|
53
|
+
autoload :BooleanType
|
54
|
+
autoload :DateType
|
55
|
+
autoload :FloatType
|
56
|
+
autoload :IntegerType
|
57
|
+
autoload :JsonType
|
58
|
+
autoload :StringType
|
59
|
+
autoload :TimeType
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'cassandra_object/railtie' if defined?(Rails)
|
64
|
+
require 'cassandra_object/rails_initializer' if defined?(Rails)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
CassandraObject::Base.config = {
|
2
|
+
keyspace: 'cassandra_object_test',
|
3
|
+
servers: '127.0.0.1:9160',
|
4
|
+
thrift: {
|
5
|
+
timeout: 5
|
6
|
+
}
|
7
|
+
}
|
8
|
+
|
9
|
+
begin
|
10
|
+
CassandraObject::Schema.drop_keyspace 'cassandra_object_test'
|
11
|
+
rescue Exception => e
|
12
|
+
end
|
13
|
+
|
14
|
+
sleep 1
|
15
|
+
CassandraObject::Schema.create_keyspace 'cassandra_object_test'
|
16
|
+
CassandraObject::Schema.create_column_family 'Issues'
|
17
|
+
CassandraObject::Base.default_consistency = 'QUORUM'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
CassandraObject::Base.class_eval do
|
2
|
+
class_attribute :created_records
|
3
|
+
self.created_records = []
|
4
|
+
|
5
|
+
after_create do
|
6
|
+
created_records << self
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.delete_after_test
|
10
|
+
# created_records.reject(&:destroyed?).each(&:destroy)
|
11
|
+
Issue.delete_all
|
12
|
+
created_records.clear
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ActiveSupport
|
17
|
+
class TestCase
|
18
|
+
teardown do
|
19
|
+
if CassandraObject::Base.created_records.any?
|
20
|
+
CassandraObject::Base.delete_after_test
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
Bundler.require(:default, :test)
|
4
|
+
|
5
|
+
require 'support/connect'
|
6
|
+
require 'support/teardown'
|
7
|
+
require 'support/issue'
|
8
|
+
|
9
|
+
module CassandraObject
|
10
|
+
class TestCase < ActiveSupport::TestCase
|
11
|
+
def temp_object(&block)
|
12
|
+
Class.new(CassandraObject::Base) do
|
13
|
+
self.column_family = 'Issues'
|
14
|
+
string :force_save
|
15
|
+
before_save { self.force_save = 'junk' }
|
16
|
+
|
17
|
+
def self.name
|
18
|
+
'Issue'
|
19
|
+
end
|
20
|
+
|
21
|
+
instance_eval(&block) if block_given?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Types
|
27
|
+
class TestCase < CassandraObject::TestCase
|
28
|
+
attr_accessor :coder
|
29
|
+
setup do
|
30
|
+
@coder = self.class.name.sub(/Test$/, '').constantize.new
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ActiveModelTest < CassandraObject::TestCase
|
4
|
+
|
5
|
+
include ActiveModel::Lint::Tests
|
6
|
+
|
7
|
+
# overrides ActiveModel::Lint::Tests#test_to_param
|
8
|
+
def test_to_param
|
9
|
+
end
|
10
|
+
|
11
|
+
# overrides ActiveModel::Lint::Tests#test_to_key
|
12
|
+
def test_to_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup
|
16
|
+
@model = Issue.new
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CassandraObject::AttributeMethods::DefinitionTest < CassandraObject::TestCase
|
4
|
+
class TestType < CassandraObject::Types::BaseType
|
5
|
+
end
|
6
|
+
|
7
|
+
test 'typecast' do
|
8
|
+
definition = CassandraObject::AttributeMethods::Definition.new(:foo, TestType, {a: :b})
|
9
|
+
|
10
|
+
assert_equal 'foo', definition.name
|
11
|
+
assert_kind_of TestType, definition.coder
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CassandraObject::AttributeMethods::DirtyTest < CassandraObject::TestCase
|
4
|
+
test 'save clears dirty' do
|
5
|
+
record = temp_object do
|
6
|
+
string :name
|
7
|
+
end.new name: 'foo'
|
8
|
+
|
9
|
+
assert record.changed?
|
10
|
+
|
11
|
+
record.save!
|
12
|
+
|
13
|
+
assert !record.changed?
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'reload clears dirty' do
|
17
|
+
record = temp_object do
|
18
|
+
string :name
|
19
|
+
end.create! name: 'foo'
|
20
|
+
|
21
|
+
record.name = 'bar'
|
22
|
+
assert record.changed?
|
23
|
+
|
24
|
+
record.reload
|
25
|
+
|
26
|
+
assert !record.changed?
|
27
|
+
end
|
28
|
+
|
29
|
+
test 'typecast float before dirty check' do
|
30
|
+
record = temp_object do
|
31
|
+
float :price
|
32
|
+
end.create(price: 5.01)
|
33
|
+
|
34
|
+
record.price = '5.01'
|
35
|
+
assert !record.changed?
|
36
|
+
|
37
|
+
record.price = '7.12'
|
38
|
+
assert record.changed?
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'typecast boolean before dirty check' do
|
42
|
+
record = temp_object do
|
43
|
+
boolean :awesome
|
44
|
+
end.create(awesome: false)
|
45
|
+
|
46
|
+
record.awesome = false
|
47
|
+
assert !record.changed?
|
48
|
+
|
49
|
+
record.awesome = true
|
50
|
+
assert record.changed?
|
51
|
+
end
|
52
|
+
|
53
|
+
test 'write_attribute' do
|
54
|
+
object = temp_object do
|
55
|
+
string :name
|
56
|
+
end
|
57
|
+
|
58
|
+
expected = {"name"=>[nil, "foo"]}
|
59
|
+
|
60
|
+
object.new.tap do |record|
|
61
|
+
record.name = 'foo'
|
62
|
+
assert_equal expected, record.changes
|
63
|
+
end
|
64
|
+
|
65
|
+
object.new.tap do |record|
|
66
|
+
record[:name] = 'foo'
|
67
|
+
# record.write_attribute(:name, 'foo')
|
68
|
+
assert_equal expected, record.changes
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|