extendi-cassandra_object 1.0.0

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 (104) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +23 -0
  4. data/CHANGELOG +0 -0
  5. data/Gemfile +17 -0
  6. data/LICENSE +13 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +177 -0
  9. data/Rakefile +12 -0
  10. data/extendi-cassandra_object.gemspec +26 -0
  11. data/lib/cassandra_object.rb +73 -0
  12. data/lib/cassandra_object/adapters/abstract_adapter.rb +61 -0
  13. data/lib/cassandra_object/adapters/cassandra_adapter.rb +269 -0
  14. data/lib/cassandra_object/adapters/cassandra_schemaless_adapter.rb +306 -0
  15. data/lib/cassandra_object/attribute_methods.rb +96 -0
  16. data/lib/cassandra_object/attribute_methods/definition.rb +22 -0
  17. data/lib/cassandra_object/attribute_methods/dirty.rb +36 -0
  18. data/lib/cassandra_object/attribute_methods/primary_key.rb +25 -0
  19. data/lib/cassandra_object/attribute_methods/typecasting.rb +59 -0
  20. data/lib/cassandra_object/base.rb +33 -0
  21. data/lib/cassandra_object/base_schema.rb +11 -0
  22. data/lib/cassandra_object/base_schemaless.rb +11 -0
  23. data/lib/cassandra_object/base_schemaless_dynamic.rb +11 -0
  24. data/lib/cassandra_object/belongs_to.rb +63 -0
  25. data/lib/cassandra_object/belongs_to/association.rb +49 -0
  26. data/lib/cassandra_object/belongs_to/builder.rb +40 -0
  27. data/lib/cassandra_object/belongs_to/reflection.rb +30 -0
  28. data/lib/cassandra_object/callbacks.rb +29 -0
  29. data/lib/cassandra_object/core.rb +63 -0
  30. data/lib/cassandra_object/errors.rb +10 -0
  31. data/lib/cassandra_object/identity.rb +26 -0
  32. data/lib/cassandra_object/inspect.rb +25 -0
  33. data/lib/cassandra_object/log_subscriber.rb +44 -0
  34. data/lib/cassandra_object/model.rb +60 -0
  35. data/lib/cassandra_object/persistence.rb +203 -0
  36. data/lib/cassandra_object/railtie.rb +33 -0
  37. data/lib/cassandra_object/railties/controller_runtime.rb +45 -0
  38. data/lib/cassandra_object/schema.rb +83 -0
  39. data/lib/cassandra_object/schemaless.rb +83 -0
  40. data/lib/cassandra_object/scope.rb +86 -0
  41. data/lib/cassandra_object/scope/finder_methods.rb +54 -0
  42. data/lib/cassandra_object/scope/query_methods.rb +69 -0
  43. data/lib/cassandra_object/scoping.rb +27 -0
  44. data/lib/cassandra_object/serialization.rb +6 -0
  45. data/lib/cassandra_object/tasks/ks.rake +54 -0
  46. data/lib/cassandra_object/timestamps.rb +19 -0
  47. data/lib/cassandra_object/type.rb +16 -0
  48. data/lib/cassandra_object/types.rb +8 -0
  49. data/lib/cassandra_object/types/array_type.rb +16 -0
  50. data/lib/cassandra_object/types/base_type.rb +26 -0
  51. data/lib/cassandra_object/types/boolean_type.rb +20 -0
  52. data/lib/cassandra_object/types/date_type.rb +22 -0
  53. data/lib/cassandra_object/types/float_type.rb +16 -0
  54. data/lib/cassandra_object/types/integer_type.rb +20 -0
  55. data/lib/cassandra_object/types/json_type.rb +13 -0
  56. data/lib/cassandra_object/types/string_type.rb +19 -0
  57. data/lib/cassandra_object/types/time_type.rb +16 -0
  58. data/lib/cassandra_object/types/type_helper.rb +39 -0
  59. data/lib/cassandra_object/validations.rb +44 -0
  60. data/test/support/cassandra.rb +63 -0
  61. data/test/support/issue.rb +12 -0
  62. data/test/support/issue_dynamic.rb +12 -0
  63. data/test/support/issue_schema.rb +17 -0
  64. data/test/support/issue_schema_child.rb +17 -0
  65. data/test/support/issue_schema_father.rb +13 -0
  66. data/test/test_helper.rb +41 -0
  67. data/test/unit/active_model_test.rb +18 -0
  68. data/test/unit/adapters/adapter_test.rb +6 -0
  69. data/test/unit/attribute_methods/definition_test.rb +13 -0
  70. data/test/unit/attribute_methods/dirty_test.rb +72 -0
  71. data/test/unit/attribute_methods/primary_key_test.rb +26 -0
  72. data/test/unit/attribute_methods/typecasting_test.rb +119 -0
  73. data/test/unit/attribute_methods_test.rb +51 -0
  74. data/test/unit/base_test.rb +20 -0
  75. data/test/unit/belongs_to/reflection_test.rb +12 -0
  76. data/test/unit/belongs_to_test.rb +63 -0
  77. data/test/unit/callbacks_test.rb +46 -0
  78. data/test/unit/connection_test.rb +6 -0
  79. data/test/unit/connections/connections_test.rb +55 -0
  80. data/test/unit/core_test.rb +55 -0
  81. data/test/unit/identity_test.rb +26 -0
  82. data/test/unit/inspect_test.rb +26 -0
  83. data/test/unit/log_subscriber_test.rb +25 -0
  84. data/test/unit/persistence_schema_test.rb +156 -0
  85. data/test/unit/persistence_test.rb +266 -0
  86. data/test/unit/railties/controller_runtime_test.rb +48 -0
  87. data/test/unit/schema/tasks_test.rb +32 -0
  88. data/test/unit/schema_test.rb +115 -0
  89. data/test/unit/schemaless_test.rb +100 -0
  90. data/test/unit/scope/finder_methods_test.rb +117 -0
  91. data/test/unit/scope/query_methods_test.rb +32 -0
  92. data/test/unit/scoping_test.rb +7 -0
  93. data/test/unit/timestamps_test.rb +27 -0
  94. data/test/unit/types/array_type_test.rb +17 -0
  95. data/test/unit/types/base_type_test.rb +19 -0
  96. data/test/unit/types/boolean_type_test.rb +24 -0
  97. data/test/unit/types/date_type_test.rb +15 -0
  98. data/test/unit/types/float_type_test.rb +17 -0
  99. data/test/unit/types/integer_type_test.rb +19 -0
  100. data/test/unit/types/json_type_test.rb +23 -0
  101. data/test/unit/types/string_type_test.rb +25 -0
  102. data/test/unit/types/time_type_test.rb +14 -0
  103. data/test/unit/validations_test.rb +27 -0
  104. metadata +202 -0
@@ -0,0 +1,27 @@
1
+ module CassandraObject
2
+ module Scoping
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ singleton_class.class_eval do
7
+ delegate :find, :find_by_id, :find_all_in_batches, :first, to: :scope
8
+ delegate :select, :where, :where_ids, to: :scope
9
+ delegate :cql_response, :columns, :limit, :per_page, to: :scope
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def scope
15
+ self.current_scope ||= Scope.new(self)
16
+ end
17
+
18
+ def current_scope
19
+ Thread.current["#{self}_current_scope"]
20
+ end
21
+
22
+ def current_scope=(new_scope)
23
+ Thread.current["#{self}_current_scope"] = new_scope
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ module CassandraObject
2
+ module Serialization
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Serializers::JSON
5
+ end
6
+ end
@@ -0,0 +1,54 @@
1
+ ks_namespace = namespace :ks do
2
+ desc 'Create the keyspace in config/cassandra.yml for the current environment'
3
+ task create: :environment do
4
+ begin
5
+ CassandraObject::Schema.create_keyspace CassandraObject::Base.config[:keyspace], CassandraObject::Base.config[:keyspace_options]
6
+ rescue Exception => e
7
+ if e.message =~ /conflicts/
8
+ p "Keyspace #{CassandraObject::Base.config[:keyspace]} already exists"
9
+ else
10
+ raise e
11
+ end
12
+ end
13
+ end
14
+
15
+ task drop: :environment do
16
+ begin
17
+ CassandraObject::Schema.drop_keyspace CassandraObject::Base.config[:keyspace]
18
+ rescue Exception => e
19
+ if e.message =~ /non existing keyspace/
20
+ p "Keyspace #{CassandraObject::Base.config[:keyspace]} does not exist"
21
+ else
22
+ raise e
23
+ end
24
+ end
25
+ end
26
+
27
+ task reset: [:drop, :setup]
28
+
29
+ task setup: [:create, :_load]
30
+
31
+ namespace :structure do
32
+ task dump: :environment do
33
+ filename = ENV['SCHEMA'] || "#{Rails.root}/ks/structure.cql"
34
+ File.open(filename, "w:utf-8") do |file|
35
+ CassandraObject::Schema.dump(file)
36
+ end
37
+ end
38
+
39
+ task load: :environment do
40
+ filename = ENV['SCHEMA'] || "#{Rails.root}/ks/structure.cql"
41
+ File.open(filename) do |file|
42
+ CassandraObject::Schema.load(file)
43
+ end
44
+ end
45
+ end
46
+
47
+ task :_dump do
48
+ ks_namespace["structure:dump"].invoke
49
+ end
50
+
51
+ task :_load do
52
+ ks_namespace["structure:load"].invoke
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ module CassandraObject
2
+ module Timestamps
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attribute :created_at, type: :time
7
+ attribute :updated_at, type: :time
8
+
9
+ before_create do
10
+ self.created_at ||= Time.current
11
+ self.updated_at ||= Time.current
12
+ end
13
+
14
+ before_update if: :changed? do
15
+ self.updated_at = Time.current
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module CassandraObject
2
+ class Type
3
+ cattr_accessor :attribute_types
4
+ self.attribute_types = {}.with_indifferent_access
5
+
6
+ class << self
7
+ def register(name, coder)
8
+ attribute_types[name] = coder
9
+ end
10
+
11
+ def get_coder(name)
12
+ attribute_types[name]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ CassandraObject::Type.register(:array, CassandraObject::Types::ArrayType)
2
+ CassandraObject::Type.register(:boolean, CassandraObject::Types::BooleanType)
3
+ CassandraObject::Type.register(:date, CassandraObject::Types::DateType)
4
+ CassandraObject::Type.register(:float, CassandraObject::Types::FloatType)
5
+ CassandraObject::Type.register(:integer, CassandraObject::Types::IntegerType)
6
+ CassandraObject::Type.register(:json, CassandraObject::Types::JsonType)
7
+ CassandraObject::Type.register(:time, CassandraObject::Types::TimeType)
8
+ CassandraObject::Type.register(:string, CassandraObject::Types::StringType)
@@ -0,0 +1,16 @@
1
+ module CassandraObject
2
+ module Types
3
+ class ArrayType < BaseType
4
+ def encode(array)
5
+ raise ArgumentError.new("#{array.inspect} is not an Array") unless array.kind_of?(Array)
6
+ array.to_a.to_json
7
+ end
8
+
9
+ def decode(str)
10
+ return nil if str.blank?
11
+
12
+ ActiveSupport::JSON.decode(str)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -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 typecast(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,22 @@
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
+ return nil if str.empty?
14
+ Date.parse(str)
15
+ end
16
+
17
+ def typecast(value)
18
+ value.to_date
19
+ end
20
+ end
21
+ end
22
+ 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,20 @@
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
+
15
+ def typecast(value)
16
+ value.to_i
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ module CassandraObject
2
+ module Types
3
+ class JsonType < BaseType
4
+ def encode(hash)
5
+ ActiveSupport::JSON.encode(hash)
6
+ end
7
+
8
+ def decode(str)
9
+ ActiveSupport::JSON.decode(str)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
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
+
7
+ unless str.encoding == Encoding::UTF_8
8
+ (str.frozen? ? str.dup : str).force_encoding('UTF-8')
9
+ else
10
+ str
11
+ end
12
+ end
13
+
14
+ def typecast(value)
15
+ value.to_s
16
+ end
17
+ end
18
+ end
19
+ 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.in_time_zone if str
11
+ rescue
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ module CassandraObject
2
+ module Types
3
+ class TypeHelper
4
+
5
+ def self.guess_type(object)
6
+ case object
7
+ when ::String then Cassandra::Types.varchar
8
+ when ::Fixnum then Cassandra::Types.int
9
+ when ::Integer then Cassandra::Types.int
10
+ when ::Float then Cassandra::Types.float
11
+ when ::Bignum then Cassandra::Types.varint
12
+ when ::BigDecimal then Cassandra::Types.decimal
13
+ when ::TrueClass then Cassandra::Types.boolean
14
+ when ::FalseClass then Cassandra::Types.boolean
15
+ when ::NilClass then Cassandra::Types.bigint
16
+ # when Uuid then Cassandra::Types.uuid
17
+ # when TimeUuid then Cassandra::Types.timeuuid
18
+ when ::IPAddr then Cassandra::Types.inet
19
+ when ::Time then Cassandra::Types.timestamp
20
+ when ::Hash
21
+ pair = object.first
22
+ Types.map(guess_type(pair[0]), guess_type(pair[1]))
23
+ when ::Array then Types.list(guess_type(object.first))
24
+ when ::Set then Types.set(guess_type(object.first))
25
+ # when Tuple::Strict then Types.tuple(*object.types)
26
+ # when Tuple then Types.tuple(*object.map {|v| guess_type(v)})
27
+ # when UDT::Strict
28
+ # Types.udt(object.keyspace, object.name, object.types)
29
+ # when UDT
30
+ # Types.udt('unknown', 'unknown', object.map {|k, v| [k, guess_type(v)]})
31
+ when Cassandra::CustomData then object.class.type
32
+ else
33
+ raise ::ArgumentError, "Unable to guess the type of the argument: #{object.inspect}"
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ 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,63 @@
1
+ Bundler.require :cassandra
2
+
3
+ CassandraObject::Base.config = {
4
+ keyspace: 'cassandra_object_test',
5
+ hosts: ['127.0.0.1'],
6
+ compression: :lz4,
7
+ connect_timeout: 1,
8
+ timeout: 30,
9
+ consistency: :quorum,
10
+ protocol_version: 3,
11
+ page_size: 10000,
12
+ trace: true,
13
+ connections_per_local_node: 4,
14
+ schema_refresh_delay: 0.1,
15
+ schema_refresh_timeout: 0.1,
16
+ # connections_per_remote_node: nil,
17
+ # logger: Logger.new($stderr)
18
+ }
19
+
20
+ begin
21
+ CassandraObject::Schema.drop_keyspace 'cassandra_object_test', true
22
+ CassandraObject::Schema.drop_keyspace 'blah', true
23
+ rescue Exception => e
24
+ puts e.message
25
+ end
26
+
27
+ sleep 1
28
+ CassandraObject::Schema.create_keyspace 'cassandra_object_test'
29
+ CassandraObject::Schemaless.create_column_family 'Issues'
30
+ CassandraObject::Schema.create_column_family 'IssueSchemas', {attributes: 'id text, title text, description text, field float, intero int, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {}}
31
+ CassandraObject::Schemaless.create_column_family 'IssueDynamics'
32
+ CassandraObject::Schema.create_column_family 'IssueSchemaFathers', {attributes: 'id text, title text, field float, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {}}
33
+ CassandraObject::Schema.create_column_family 'IssueSchemaChildren', {attributes: 'id text, title text, description text, field float, created_at timestamp, updated_at timestamp, issue_schema_father_id text, PRIMARY KEY (id)', options: {}}
34
+ CassandraObject::BaseSchemaless.adapter.consistency = :quorum
35
+ CassandraObject::BaseSchemalessDynamic.adapter.consistency = :quorum
36
+ CassandraObject::BaseSchema.adapter.consistency = :quorum
37
+
38
+ CassandraObject::Base.class_eval do
39
+ class_attribute :created_records
40
+ self.created_records = []
41
+
42
+ after_create do
43
+ created_records << self
44
+ end
45
+
46
+ def self.delete_after_test
47
+ # created_records.reject(&:destroyed?).each(&:destroy)
48
+ Issue.delete_all
49
+ IssueSchema.delete_all
50
+ IssueDynamic.delete_all
51
+ created_records.clear
52
+ end
53
+ end
54
+
55
+ module ActiveSupport
56
+ class TestCase
57
+ teardown do
58
+ if CassandraObject::Base.created_records.any?
59
+ CassandraObject::Base.delete_after_test
60
+ end
61
+ end
62
+ end
63
+ end