cassandra_object_rails 0.0.1

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