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.
Files changed (97) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG +5 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE +13 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.rdoc +97 -0
  9. data/Rakefile +12 -0
  10. data/cassandra_object_rails.gemspec +26 -0
  11. data/lib/cassandra_object/attribute_methods.rb +87 -0
  12. data/lib/cassandra_object/attribute_methods/definition.rb +19 -0
  13. data/lib/cassandra_object/attribute_methods/dirty.rb +44 -0
  14. data/lib/cassandra_object/attribute_methods/primary_key.rb +25 -0
  15. data/lib/cassandra_object/attribute_methods/typecasting.rb +59 -0
  16. data/lib/cassandra_object/base.rb +69 -0
  17. data/lib/cassandra_object/belongs_to.rb +63 -0
  18. data/lib/cassandra_object/belongs_to/association.rb +48 -0
  19. data/lib/cassandra_object/belongs_to/builder.rb +40 -0
  20. data/lib/cassandra_object/belongs_to/reflection.rb +30 -0
  21. data/lib/cassandra_object/callbacks.rb +29 -0
  22. data/lib/cassandra_object/config.rb +15 -0
  23. data/lib/cassandra_object/connection.rb +36 -0
  24. data/lib/cassandra_object/consistency.rb +18 -0
  25. data/lib/cassandra_object/core.rb +59 -0
  26. data/lib/cassandra_object/errors.rb +6 -0
  27. data/lib/cassandra_object/identity.rb +24 -0
  28. data/lib/cassandra_object/inspect.rb +25 -0
  29. data/lib/cassandra_object/log_subscriber.rb +29 -0
  30. data/lib/cassandra_object/persistence.rb +169 -0
  31. data/lib/cassandra_object/rails_initializer.rb +19 -0
  32. data/lib/cassandra_object/railtie.rb +11 -0
  33. data/lib/cassandra_object/savepoints.rb +79 -0
  34. data/lib/cassandra_object/schema.rb +78 -0
  35. data/lib/cassandra_object/schema/tasks.rb +48 -0
  36. data/lib/cassandra_object/scope.rb +48 -0
  37. data/lib/cassandra_object/scope/batches.rb +32 -0
  38. data/lib/cassandra_object/scope/finder_methods.rb +47 -0
  39. data/lib/cassandra_object/scope/query_methods.rb +111 -0
  40. data/lib/cassandra_object/scoping.rb +19 -0
  41. data/lib/cassandra_object/serialization.rb +6 -0
  42. data/lib/cassandra_object/tasks/cassandra.rake +53 -0
  43. data/lib/cassandra_object/timestamps.rb +19 -0
  44. data/lib/cassandra_object/type.rb +16 -0
  45. data/lib/cassandra_object/types.rb +8 -0
  46. data/lib/cassandra_object/types/array_type.rb +76 -0
  47. data/lib/cassandra_object/types/base_type.rb +26 -0
  48. data/lib/cassandra_object/types/boolean_type.rb +20 -0
  49. data/lib/cassandra_object/types/date_type.rb +17 -0
  50. data/lib/cassandra_object/types/float_type.rb +16 -0
  51. data/lib/cassandra_object/types/integer_type.rb +16 -0
  52. data/lib/cassandra_object/types/json_type.rb +52 -0
  53. data/lib/cassandra_object/types/string_type.rb +15 -0
  54. data/lib/cassandra_object/types/time_type.rb +16 -0
  55. data/lib/cassandra_object/validations.rb +44 -0
  56. data/lib/cassandra_object_rails.rb +64 -0
  57. data/test/support/connect.rb +17 -0
  58. data/test/support/issue.rb +5 -0
  59. data/test/support/teardown.rb +24 -0
  60. data/test/test_helper.rb +34 -0
  61. data/test/unit/active_model_test.rb +18 -0
  62. data/test/unit/attribute_methods/definition_test.rb +13 -0
  63. data/test/unit/attribute_methods/dirty_test.rb +71 -0
  64. data/test/unit/attribute_methods/primary_key_test.rb +26 -0
  65. data/test/unit/attribute_methods/typecasting_test.rb +112 -0
  66. data/test/unit/attribute_methods_test.rb +39 -0
  67. data/test/unit/base_test.rb +20 -0
  68. data/test/unit/belongs_to/reflection_test.rb +12 -0
  69. data/test/unit/belongs_to_test.rb +62 -0
  70. data/test/unit/callbacks_test.rb +46 -0
  71. data/test/unit/config_test.rb +23 -0
  72. data/test/unit/connection_test.rb +10 -0
  73. data/test/unit/consistency_test.rb +13 -0
  74. data/test/unit/core_test.rb +55 -0
  75. data/test/unit/identity_test.rb +26 -0
  76. data/test/unit/inspect_test.rb +26 -0
  77. data/test/unit/log_subscriber_test.rb +22 -0
  78. data/test/unit/persistence_test.rb +187 -0
  79. data/test/unit/savepoints_test.rb +35 -0
  80. data/test/unit/schema/tasks_test.rb +29 -0
  81. data/test/unit/schema_test.rb +47 -0
  82. data/test/unit/scope/batches_test.rb +30 -0
  83. data/test/unit/scope/finder_methods_test.rb +51 -0
  84. data/test/unit/scope/query_methods_test.rb +26 -0
  85. data/test/unit/scoping_test.rb +7 -0
  86. data/test/unit/timestamps_test.rb +27 -0
  87. data/test/unit/types/array_type_test.rb +71 -0
  88. data/test/unit/types/base_type_test.rb +24 -0
  89. data/test/unit/types/boolean_type_test.rb +24 -0
  90. data/test/unit/types/date_type_test.rb +11 -0
  91. data/test/unit/types/float_type_test.rb +17 -0
  92. data/test/unit/types/integer_type_test.rb +19 -0
  93. data/test/unit/types/json_type_test.rb +77 -0
  94. data/test/unit/types/string_type_test.rb +32 -0
  95. data/test/unit/types/time_type_test.rb +14 -0
  96. data/test/unit/validations_test.rb +27 -0
  97. 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,5 @@
1
+ class Issue < CassandraObject::Base
2
+ string :description
3
+ string :title
4
+ before_create { self.description ||= 'funny' }
5
+ end
@@ -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
@@ -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