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,25 @@
1
+ module CassandraObject
2
+ module Inspect
3
+ def inspect
4
+ inspection = ["#{self.class.primary_key}: #{id.inspect}"]
5
+
6
+ @attributes.each do |attribute, value|
7
+ if value.present?
8
+ inspection << "#{attribute}: #{attribute_for_inspect(value)}"
9
+ end
10
+ end
11
+
12
+ "#<#{self.class} #{inspection * ', '}>"
13
+ end
14
+
15
+ def attribute_for_inspect(value)
16
+ if value.is_a?(String) && value.length > 50
17
+ "#{value[0..50]}...".inspect
18
+ elsif value.is_a?(Date) || value.is_a?(Time)
19
+ %("#{value.to_s(:db)}")
20
+ else
21
+ value.inspect
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module CassandraObject
2
+ class LogSubscriber < ActiveSupport::LogSubscriber
3
+ def initialize
4
+ super
5
+ @odd_or_even = false
6
+ end
7
+
8
+ def cql(event)
9
+ payload = event.payload
10
+ name = '%s (%.1fms)' % [payload[:name], event.duration]
11
+ cql = payload[:cql].squeeze(' ')
12
+
13
+ if odd?
14
+ name = color(name, CYAN, true)
15
+ cql = color(cql, nil, true)
16
+ else
17
+ name = color(name, MAGENTA, true)
18
+ end
19
+
20
+ debug " #{name} #{cql}"
21
+ end
22
+
23
+ def odd?
24
+ @odd_or_even = !@odd_or_even
25
+ end
26
+ end
27
+ end
28
+
29
+ CassandraObject::LogSubscriber.attach_to :cassandra_object
@@ -0,0 +1,169 @@
1
+ module CassandraObject
2
+ module Persistence
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :batch_statements
7
+ end
8
+
9
+ module ClassMethods
10
+ def remove(id)
11
+ execute_batchable_cql "DELETE FROM #{column_family}#{write_option_string} WHERE KEY = ?", id
12
+ end
13
+
14
+ def delete_all
15
+ execute_cql "TRUNCATE #{column_family}"
16
+ end
17
+
18
+ def create(attributes = {}, &block)
19
+ new(attributes, &block).tap do |object|
20
+ object.save
21
+ end
22
+ end
23
+
24
+ def write(id, attributes)
25
+ if (encoded = encode_attributes(attributes)).any?
26
+ insert_attributes = {'KEY' => id}.update encode_attributes(attributes)
27
+ statement = "INSERT INTO #{column_family} (#{quote_columns(insert_attributes.keys) * ','}) VALUES (#{Array.new(insert_attributes.size, '?') * ','})#{write_option_string}"
28
+ execute_batchable_cql statement, *insert_attributes.values
29
+ end
30
+
31
+ if (nil_attributes = attributes.select { |key, value| value.nil? }).any?
32
+ execute_batchable_cql "DELETE #{quote_columns(nil_attributes.keys) * ','} FROM #{column_family}#{write_option_string} WHERE KEY = ?", id
33
+ end
34
+ end
35
+
36
+ def batching?
37
+ !batch_statements.nil?
38
+ end
39
+
40
+ def batch
41
+ self.batch_statements = []
42
+ yield
43
+ execute_cql(batch_statement) if batch_statements.any?
44
+ ensure
45
+ self.batch_statements = nil
46
+ end
47
+
48
+ def instantiate(id, attributes)
49
+ allocate.tap do |object|
50
+ object.instance_variable_set("@id", id) if id
51
+ object.instance_variable_set("@new_record", false)
52
+ object.instance_variable_set("@destroyed", false)
53
+ object.instance_variable_set("@attributes", typecast_attributes(object, attributes))
54
+ end
55
+ end
56
+
57
+ def encode_attributes(attributes)
58
+ encoded = {}
59
+ attributes.each do |column_name, value|
60
+ unless value.nil?
61
+ encoded[column_name.to_s] = attribute_definitions[column_name.to_sym].coder.encode(value)
62
+ end
63
+ end
64
+ encoded
65
+ end
66
+
67
+ private
68
+
69
+ def quote_columns(column_names)
70
+ column_names.map { |name| "'#{name}'" }
71
+ end
72
+
73
+ def batch_statement
74
+ return nil unless batch_statements.any?
75
+
76
+ [
77
+ "BEGIN BATCH#{write_option_string(true)}",
78
+ batch_statements * "\n",
79
+ 'APPLY BATCH'
80
+ ] * "\n"
81
+ end
82
+
83
+ def execute_batchable_cql(cql_string, *bind_vars)
84
+ if batch_statements
85
+ batch_statements << CassandraCQL::Statement.sanitize(cql_string, bind_vars)
86
+ else
87
+ execute_cql cql_string, *bind_vars
88
+ end
89
+ end
90
+
91
+ def typecast_attributes(object, attributes)
92
+ attributes = attributes.symbolize_keys
93
+ Hash[attribute_definitions.map { |k, attribute_definition| [k.to_s, attribute_definition.instantiate(object, attributes[k])] }]
94
+ end
95
+
96
+ def write_option_string(ignore_batching = false)
97
+ if (ignore_batching || !batching?) && base_class.default_consistency
98
+ " USING CONSISTENCY #{base_class.default_consistency}"
99
+ end
100
+ end
101
+ end
102
+
103
+ def new_record?
104
+ @new_record
105
+ end
106
+
107
+ def destroyed?
108
+ @destroyed
109
+ end
110
+
111
+ def persisted?
112
+ !(new_record? || destroyed?)
113
+ end
114
+
115
+ def save(*)
116
+ new_record? ? create : update
117
+ end
118
+
119
+ def destroy
120
+ self.class.remove(id)
121
+ @destroyed = true
122
+ end
123
+
124
+ def update_attribute(name, value)
125
+ name = name.to_s
126
+ send("#{name}=", value)
127
+ save(validate: false)
128
+ end
129
+
130
+ def update_attributes(attributes)
131
+ self.attributes = attributes
132
+ save
133
+ end
134
+
135
+ def update_attributes!(attributes)
136
+ self.attributes = attributes
137
+ save!
138
+ end
139
+
140
+ def becomes(klass)
141
+ became = klass.new
142
+ became.instance_variable_set("@attributes", @attributes)
143
+ became.instance_variable_set("@new_record", new_record?)
144
+ became.instance_variable_set("@destroyed", destroyed?)
145
+ became
146
+ end
147
+
148
+ def reload
149
+ clear_belongs_to_cache
150
+ @attributes.update(self.class.find(id).instance_variable_get('@attributes'))
151
+ self
152
+ end
153
+
154
+ private
155
+ def update
156
+ write
157
+ end
158
+
159
+ def create
160
+ @new_record = false
161
+ write
162
+ end
163
+
164
+ def write
165
+ changed_attributes = Hash[changed.map { |attr| [attr, read_attribute(attr)] }]
166
+ self.class.write(id, changed_attributes)
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,19 @@
1
+ module CassandraObject
2
+
3
+ class RailsInitializer
4
+
5
+ def self.configure!
6
+ self.new.configure!
7
+ end
8
+
9
+ def configure!
10
+ return if cassandra_configs.nil?
11
+ CassandraObject::Base.config = cassandra_configs[Rails.env || 'development']
12
+ end
13
+
14
+ def cassandra_configs
15
+ @config ||= YAML.load_file(Rails.root.join('config', 'cassandra.yml'))
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module CassandraObject
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load 'cassandra_object/tasks/cassandra.rake'
5
+ end
6
+
7
+ initializer 'my_railtie.configure_rails_initialization' do |app|
8
+ RailsInitializer.configure!
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,79 @@
1
+ module CassandraObject
2
+ module Savepoints
3
+ class Rollback
4
+ attr_accessor :action, :record, :rollback_attributes
5
+ def initialize(action, record)
6
+ @action = action
7
+ @record = record
8
+ if action == :update
9
+ @rollback_attributes = record.changed_attributes.deep_dup
10
+ elsif action == :create
11
+ @rollback_attributes = record.attributes.deep_dup
12
+ end
13
+ end
14
+
15
+ def run!
16
+ case action
17
+ when :destroy
18
+ record.class.remove(record.id)
19
+ when :create, :update
20
+ record.class.write(record.id, rollback_attributes)
21
+ end
22
+ end
23
+ end
24
+
25
+ class Savepoint
26
+ attr_accessor :rollbacks
27
+ def initialize
28
+ self.rollbacks = []
29
+ end
30
+
31
+ def add_rollback(action, record)
32
+ states << Rollback.new(action, record)
33
+ end
34
+
35
+ def rollback!
36
+ rollbacks.reverse_each(&:run!)
37
+ end
38
+ end
39
+
40
+ extend ActiveSupport::Concern
41
+
42
+ included do
43
+ class_attribute :savepoints
44
+ self.savepoints = []
45
+ end
46
+
47
+ module ClassMethods
48
+ def savepoint
49
+ self.savepoints.push Savepoint.new
50
+ yield
51
+ savepoints.pop
52
+ rescue => e
53
+ savepoints.pop.rollback!
54
+ end
55
+
56
+ def add_savepoint_rollback(action, record)
57
+ unless savepoints.empty?
58
+ savepoints.last.add_rollback(action, record)
59
+ end
60
+ end
61
+ end
62
+
63
+ def destroy
64
+ self.class.add_savepoint_rollback(:create, self)
65
+ super
66
+ end
67
+
68
+ private
69
+ def create
70
+ self.class.add_savepoint_rollback(:destroy, self)
71
+ super
72
+ end
73
+
74
+ def update
75
+ self.class.add_savepoint_rollback(:update, self)
76
+ super
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,78 @@
1
+ require 'cassandra_object/schema/tasks'
2
+
3
+ module CassandraObject
4
+ class Schema
5
+ extend Tasks
6
+
7
+ class << self
8
+ DEFAULT_CREATE_KEYSPACE = {
9
+ 'strategy_class' => 'SimpleStrategy',
10
+ 'strategy_options:replication_factor' => 1
11
+ }
12
+
13
+ def create_keyspace(keyspace, options = {})
14
+ stmt = "CREATE KEYSPACE #{keyspace}"
15
+
16
+ if options.empty?
17
+ options = DEFAULT_CREATE_KEYSPACE
18
+ end
19
+
20
+ system_execute statement_with_options(stmt, options)
21
+ end
22
+
23
+ def drop_keyspace(keyspace)
24
+ system_execute "DROP KEYSPACE #{keyspace}"
25
+ end
26
+
27
+ def create_column_family(column_family, options = {})
28
+ stmt = "CREATE COLUMNFAMILY #{column_family} " +
29
+ "(KEY varchar PRIMARY KEY)"
30
+
31
+ execute statement_with_options(stmt, options)
32
+ end
33
+
34
+ def alter_column_family(column_family, instruction, options = {})
35
+ stmt = "ALTER TABLE #{column_family} #{instruction}"
36
+ execute statement_with_options(stmt, options)
37
+ end
38
+
39
+ def drop_column_family(column_family)
40
+ stmt = "DROP TABLE #{column_family}"
41
+ execute stmt
42
+ end
43
+
44
+ def add_index(column_family, column, index_name = nil)
45
+ stmt = "CREATE INDEX #{index_name.nil? ? '' : index_name} ON #{column_family} (#{column})"
46
+ execute stmt
47
+ end
48
+
49
+ def drop_index(index_name)
50
+ # If the index was not given a name during creation, the index name is <columnfamily_name>_<column_name>_idx.
51
+ stmt = "DROP INDEX #{index_name}"
52
+ execute stmt
53
+ end
54
+
55
+ private
56
+ def statement_with_options(stmt, options)
57
+ if options.any?
58
+ with_stmt = options.map do |k,v|
59
+ "#{k} = #{CassandraCQL::Statement.quote(v)}"
60
+ end.join(' AND ')
61
+
62
+ stmt << " WITH #{with_stmt}"
63
+ end
64
+
65
+ stmt
66
+ end
67
+
68
+ def execute(cql)
69
+ CassandraObject::Base.execute_cql cql
70
+ end
71
+
72
+ def system_execute(cql)
73
+ @system_cql ||= CassandraCQL::Database.new(CassandraObject::Base.config.servers, keyspace: 'system')
74
+ @system_cql.execute cql
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,48 @@
1
+ module CassandraObject
2
+ class Schema
3
+ module Tasks
4
+ def dump(io)
5
+ column_families.each do |column_family|
6
+ io.puts run_command("DESCRIBE COLUMNFAMILY #{column_family}")
7
+ io.puts
8
+ end
9
+ end
10
+
11
+ def load(io)
12
+ current_cql = ''
13
+
14
+ io.each_line do |line|
15
+ next if line.blank?
16
+
17
+ current_cql << line.rstrip
18
+
19
+ if current_cql =~ /;$/
20
+ CassandraObject::Base.execute_cql current_cql
21
+ current_cql = ''
22
+ end
23
+ end
24
+ end
25
+
26
+ def column_families
27
+ run_command('DESCRIBE COLUMNFAMILIES').split.sort
28
+ end
29
+
30
+ private
31
+ def run_command(command)
32
+ `echo "#{command};" | #{cqlsh} -k #{keyspace} #{server}`.sub(/^(.*)$/, '').strip
33
+ end
34
+
35
+ def cqlsh
36
+ ENV['CQLSH'] || 'cqlsh'
37
+ end
38
+
39
+ def keyspace
40
+ CassandraObject::Base.config.keyspace
41
+ end
42
+
43
+ def server
44
+ CassandraObject::Base.config.servers.first.gsub(/:.*/, '')
45
+ end
46
+ end
47
+ end
48
+ end