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,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