extendi-cassandra_object 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,33 @@
1
+ module CassandraObject
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load 'cassandra_object/tasks/ks.rake'
5
+ end
6
+
7
+ initializer "cassandra_object.log_runtime" do |app|
8
+ ActiveSupport.on_load :cassandra_object do
9
+ pathname = Rails.root.join('config', 'cassandra.yml')
10
+ if pathname.exist?
11
+ config = YAML.load(pathname.read)
12
+
13
+ if config = config[Rails.env]
14
+ self.config = {
15
+ keyspace: config['keyspace'],
16
+ hosts: config['hosts'],
17
+ }
18
+ else
19
+ raise "Missing environment #{Rails.env} in cassandra.yml"
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # Expose database runtime to controller for logging.
26
+ initializer "cassandra_object.log_runtime" do |app|
27
+ require "cassandra_object/railties/controller_runtime"
28
+ ActiveSupport.on_load(:action_controller) do
29
+ include CassandraObject::Railties::ControllerRuntime
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ require 'active_support/core_ext/module/attr_internal'
2
+ require 'cassandra_object/log_subscriber'
3
+
4
+ module CassandraObject
5
+ module Railties # :nodoc:
6
+ module ControllerRuntime #:nodoc:
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods # :nodoc:
10
+ def log_process_action(payload)
11
+ messages, cassandra_object_runtime = super, payload[:cassandra_object_runtime]
12
+ if cassandra_object_runtime.to_i > 0
13
+ messages << ("CassandraObject: %.1fms" % cassandra_object_runtime.to_f)
14
+ end
15
+ messages
16
+ end
17
+ end
18
+
19
+ #private
20
+
21
+ attr_internal :cassandra_object_runtime
22
+
23
+ def process_action(action, *args)
24
+ # We also need to reset the runtime before each action
25
+ # because of queries in middleware or in cases we are streaming
26
+ # and it won't be cleaned up by the method below.
27
+ CassandraObject::LogSubscriber.reset_runtime
28
+ super
29
+ end
30
+
31
+ def cleanup_view_runtime
32
+ runtime_before_render = CassandraObject::LogSubscriber.reset_runtime
33
+ runtime = super
34
+ runtime_after_render = CassandraObject::LogSubscriber.reset_runtime
35
+ self.cassandra_object_runtime = runtime_before_render + runtime_after_render
36
+ runtime - runtime_after_render
37
+ end
38
+
39
+ def append_info_to_payload(payload)
40
+ super
41
+ payload[:cassandra_object_runtime] = (cassandra_object_runtime || 0) + CassandraObject::LogSubscriber.reset_runtime
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,83 @@
1
+ require 'cassandra'
2
+
3
+ module CassandraObject
4
+ class Schema
5
+
6
+ class << self
7
+ DEFAULT_CREATE_KEYSPACE = {
8
+ 'strategy_class' => 'SimpleStrategy',
9
+ 'strategy_options' => 'replication_factor:1'
10
+ }
11
+
12
+ def create_keyspace(keyspace, options = nil)
13
+ stmt = "CREATE KEYSPACE #{keyspace} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"
14
+
15
+ options ||= {} #DEFAULT_CREATE_KEYSPACE
16
+
17
+ system_execute stmt #adapter.statement_with_options(stmt, options)
18
+ end
19
+
20
+ def drop_keyspace(keyspace, confirm = false)
21
+ if adapter.cassandra_version < 3
22
+ count = (system_execute "SELECT count(*) FROM schema_columnfamilies where keyspace_name = '#{keyspace}' ALLOW FILTERING;").rows.first['count']
23
+ else
24
+ count = (system_schema_execute "SELECT count(*) FROM tables where keyspace_name = '#{keyspace}';").rows.first['count']
25
+ end
26
+ if confirm || count == 0
27
+ system_execute "DROP KEYSPACE #{keyspace}"
28
+ else
29
+ raise "Cannot drop keyspace #{keyspace}. You must delete all tables before"
30
+ end
31
+ end
32
+
33
+ def create_column_family(column_family, options = {})
34
+ create_table column_family, options
35
+ end
36
+
37
+ def create_table(table_name, options = {})
38
+ adapter.create_table table_name, options
39
+ end
40
+
41
+ def alter_column_family(column_family, instruction, options = {})
42
+ stmt = "ALTER TABLE #{column_family} #{instruction}"
43
+ keyspace_execute adapter.statement_with_options(stmt, options)
44
+ end
45
+
46
+ def drop_column_family(column_family)
47
+ drop_table column_family
48
+ end
49
+
50
+ def drop_table(table_name, confirm = false)
51
+ adapter.drop_table table_name, confirm
52
+ end
53
+
54
+ def add_index(column_family, column, index_name = nil)
55
+ stmt = "CREATE INDEX #{index_name.nil? ? '' : index_name} ON #{column_family} (#{column})"
56
+ keyspace_execute stmt
57
+ end
58
+
59
+ # If the index was not given a name during creation, the index name is <columnfamily_name>_<column_name>_idx.
60
+ def drop_index(index_name)
61
+ keyspace_execute "DROP INDEX #{index_name}"
62
+ end
63
+
64
+ private
65
+
66
+ def adapter
67
+ @adapter ||= CassandraObject::Adapters::CassandraAdapter.new(CassandraObject::Base.config)
68
+ end
69
+
70
+ def keyspace_execute(cql)
71
+ adapter.schema_execute cql, CassandraObject::Base.config[:keyspace]
72
+ end
73
+
74
+ def system_schema_execute(cql)
75
+ adapter.schema_execute cql, 'system_schema'
76
+ end
77
+
78
+ def system_execute(cql)
79
+ adapter.schema_execute cql, 'system'
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,83 @@
1
+ require 'cassandra'
2
+
3
+ module CassandraObject
4
+ class Schemaless
5
+
6
+ class << self
7
+ DEFAULT_CREATE_KEYSPACE = {
8
+ 'strategy_class' => 'SimpleStrategy',
9
+ 'strategy_options' => 'replication_factor:1'
10
+ }
11
+
12
+ def create_keyspace(keyspace, options = nil)
13
+ stmt = "CREATE KEYSPACE #{keyspace} WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };"
14
+
15
+ options ||= {} #DEFAULT_CREATE_KEYSPACE
16
+
17
+ system_execute stmt #adapter.statement_with_options(stmt, options)
18
+ end
19
+
20
+ def drop_keyspace(keyspace, confirm = false)
21
+ if adapter.cassandra_version < 3
22
+ count = (system_execute "SELECT count(*) FROM schema_columnfamilies where keyspace_name = '#{keyspace}' ALLOW FILTERING;").rows.first['count']
23
+ else
24
+ count = (system_schema_execute "SELECT count(*) FROM tables where keyspace_name = '#{keyspace}';").rows.first['count']
25
+ end
26
+ if confirm || count == 0
27
+ system_execute "DROP KEYSPACE #{keyspace}"
28
+ else
29
+ raise "Cannot drop keyspace #{keyspace}. You must delete all tables before"
30
+ end
31
+ end
32
+
33
+ def create_column_family(column_family, options = {})
34
+ create_table column_family, options
35
+ end
36
+
37
+ def create_table(table_name, options = {})
38
+ adapter.create_table table_name, options
39
+ end
40
+
41
+ def alter_column_family(column_family, instruction, options = {})
42
+ stmt = "ALTER TABLE #{column_family} #{instruction}"
43
+ keyspace_execute adapter.statement_with_options(stmt, options)
44
+ end
45
+
46
+ def drop_column_family(column_family)
47
+ drop_table column_family
48
+ end
49
+
50
+ def drop_table(table_name, confirm = false)
51
+ adapter.drop_table table_name, confirm
52
+ end
53
+
54
+ def add_index(column_family, column, index_name = nil)
55
+ stmt = "CREATE INDEX #{index_name.nil? ? '' : index_name} ON #{column_family} (#{column})"
56
+ keyspace_execute stmt
57
+ end
58
+
59
+ # If the index was not given a name during creation, the index name is <columnfamily_name>_<column_name>_idx.
60
+ def drop_index(index_name)
61
+ keyspace_execute "DROP INDEX #{index_name}"
62
+ end
63
+
64
+ private
65
+
66
+ def adapter
67
+ @adapter ||= CassandraObject::Adapters::CassandraSchemalessAdapter.new(CassandraObject::Base.config)
68
+ end
69
+
70
+ def keyspace_execute(cql)
71
+ adapter.schema_execute cql, CassandraObject::Base.config[:keyspace]
72
+ end
73
+
74
+ def system_schema_execute(cql)
75
+ adapter.schema_execute cql, 'system_schema'
76
+ end
77
+
78
+ def system_execute(cql)
79
+ adapter.schema_execute cql, 'system'
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,86 @@
1
+ require 'cassandra_object/scope/finder_methods'
2
+ require 'cassandra_object/scope/query_methods'
3
+
4
+ module CassandraObject
5
+ class Scope
6
+ include FinderMethods, QueryMethods
7
+
8
+ attr_accessor :klass
9
+ attr_accessor :is_all, :limit_value, :select_values, :where_values, :id_values, :raw_response, :next_cursor
10
+
11
+ def initialize(klass)
12
+ @klass = klass
13
+
14
+ @is_all = false
15
+ @limit_value = nil
16
+ @raw_response = nil
17
+ @select_values = []
18
+ @id_values = []
19
+ @where_values = []
20
+ @next_cursor = nil
21
+ end
22
+
23
+ private
24
+
25
+ def scoping
26
+ previous, klass.current_scope = klass.current_scope, self
27
+ yield
28
+ ensure
29
+ klass.current_scope = previous
30
+ end
31
+
32
+
33
+ def method_missing(method_name, *args, &block)
34
+ if klass.respond_to?(method_name)
35
+ scoping { klass.send(method_name, *args, &block) }
36
+ elsif Array.method_defined?(method_name)
37
+ to_a.send(method_name, *args, &block)
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def select_records
44
+ results = []
45
+ records = {}
46
+ new_next_cursor = nil
47
+
48
+ if self.schema_type == :standard
49
+ klass.adapter.select(self) do |key, attributes|
50
+ records[key] = attributes
51
+ end
52
+ else
53
+ if @is_all
54
+ pre = klass.adapter.pre_select(self, @limit_value, @next_cursor)
55
+ new_next_cursor = pre[:new_next_cursor]
56
+ return {results: [], next_cursor: new_next_cursor} if pre[:ids].empty? # fix last query all if ids is empty
57
+ self.id_values = pre[:ids]
58
+ end
59
+
60
+ resp = klass.adapter.select(self)
61
+ primary_key_column = klass.adapter.primary_key_column
62
+ resp.each do |cql_row|
63
+ key = cql_row[primary_key_column]
64
+ records[key] ||= {}
65
+ records[key][cql_row.values[1]] = cql_row.values[2]
66
+ end
67
+
68
+ end
69
+ # limit
70
+ records = records.first(@limit_value) if @limit_value.present?
71
+ records.each do |key, attributes|
72
+ if self.raw_response || self.schema_type == :dynamic_attributes
73
+ results << {key => attributes.values.compact.empty? ? attributes.keys : attributes}
74
+ else
75
+ results << klass.instantiate(key, attributes)
76
+ end
77
+ end
78
+ results = results.reduce({}, :merge) if self.schema_type == :dynamic_attributes
79
+ if @is_all
80
+ return {results: results, next_cursor: new_next_cursor}
81
+ end
82
+ return results
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,54 @@
1
+ module CassandraObject
2
+ class Scope
3
+ module FinderMethods
4
+ def find(ids)
5
+ if ids.is_a?(Array)
6
+ find_some(ids)
7
+ else
8
+ find_one(ids)
9
+ end
10
+ end
11
+
12
+ def find_by_id(ids)
13
+ find(ids)
14
+ rescue CassandraObject::RecordNotFound
15
+ nil
16
+ end
17
+
18
+ def find_all_in_batches(next_cursor = nil)
19
+ obj = self.clone
20
+ obj.is_all = true
21
+ obj.next_cursor = next_cursor
22
+ obj.to_a
23
+ end
24
+
25
+ def first
26
+ return limit(1).find_all_in_batches[:results].first if self.schema_type == :dynamic_attributes || self.schema_type == :schemaless
27
+ limit(1).to_a.first
28
+ end
29
+
30
+ private
31
+
32
+ def find_one(id)
33
+ if id.blank?
34
+ raise CassandraObject::RecordNotFound, "Couldn't find #{self.name} with key #{id.inspect}"
35
+ elsif self.schema_type == :dynamic_attributes
36
+ record = where_ids(id).to_a
37
+ raise CassandraObject::RecordNotFound if record.empty?
38
+ record
39
+ elsif record = where_ids(id)[0]
40
+ record
41
+ else
42
+ raise CassandraObject::RecordNotFound
43
+ end
44
+ end
45
+
46
+ def find_some(ids)
47
+ ids = ids.flatten
48
+ return [] if ids.empty?
49
+ ids = ids.compact.map(&:to_s).uniq
50
+ where_ids(ids).to_a
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,69 @@
1
+ module CassandraObject
2
+ class Scope
3
+ module QueryMethods
4
+
5
+ def cql_response
6
+ cloned = self.clone
7
+ cloned.raw_response = true
8
+ cloned
9
+ end
10
+
11
+ def columns
12
+ cloned = self.clone
13
+ cloned.select_values = [:column1]
14
+ cloned.cql_response
15
+ end
16
+
17
+ def select!(*values)
18
+ self.select_values += values.flatten
19
+ self
20
+ end
21
+
22
+ def select(*values, &block)
23
+ if block_given?
24
+ to_a.select(&block)
25
+ else
26
+ clone.select!(*values)
27
+ end
28
+ end
29
+
30
+ def where!(*values)
31
+ if values.flatten.size == 1
32
+ self.where_values += values.flatten
33
+ self.where_values << ''
34
+ else
35
+ self.where_values += values.flatten
36
+ end
37
+ self
38
+ end
39
+
40
+ def where(*values)
41
+ clone.where! values
42
+ end
43
+
44
+ def where_ids!(*ids)
45
+ self.id_values += ids.flatten
46
+ self.id_values.compact if self.id_values.present?
47
+ self
48
+ end
49
+
50
+ def where_ids(*ids)
51
+ clone.where_ids! ids
52
+ end
53
+
54
+ def limit!(value)
55
+ self.limit_value = value
56
+ self
57
+ end
58
+
59
+ def limit(value)
60
+ clone.limit! value
61
+ end
62
+
63
+ def to_a
64
+ select_records
65
+ end
66
+
67
+ end
68
+ end
69
+ end