cassandra_object_rails 0.0.1
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.
- 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
|