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