cassandro 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NmU5NmI3YWNjYjMwYTYyZjQ2YjNlOGM1NTc4NTRkMmZkNDIwNjNlOA==
5
+ data.tar.gz: !binary |-
6
+ NDZkYjJiMmZlOWU1ZTRjZDk4MmI2N2IzODFjYzJiYzY2OWI3M2Q3Yg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MDJiNDg1MTkyYWRmNzVjYzc0N2FkZjEzOGZmYThjMjRhZDViMmMwNjU4Yjgy
10
+ ZDJjYzM4NTliYTdhNmNlZjI3MDRmMDgwZjc1ZTQ3ODQ1ZjQ4ZTNhYjEwOTcz
11
+ ZmMxNjM4NjRjYjlhNjJiMWZiNTY4MjljZDNlODJjYjZkM2NmY2I=
12
+ data.tar.gz: !binary |-
13
+ MGU1MjY0ZTE5NzlkNDIyMWE0N2QzODllZmRhZDZiMzBlNWI3NmI5NmZjY2E4
14
+ ZjQ5YTI1OWQwYTZhMjM0OTEyY2VmMDYxYzdjY2RmNjViMDVmZDJlYTVkMTgx
15
+ YmRjYjAyYzM2OWRiMjI0YmIxZGZkM2UxYmRkYTc0MDE5MDQ5ZDI=
data/.gems ADDED
@@ -0,0 +1 @@
1
+ cassandra-driver -v 1.0.0.beta.3
data/.gems-test ADDED
@@ -0,0 +1 @@
1
+ protest -v 0.5.2
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright (C) 2014 Lautaro Orazi and Leonardo Mateo
2
+
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,187 @@
1
+ # Cassandro
2
+
3
+ Cassandro is a small Ruby ORM for Apache Cassandra 2.0 and CQL 3.0. Cassandro uses the new Datastax Ruby Driver (official driver).
4
+
5
+ ## Install
6
+
7
+ `gem install cassandro`
8
+
9
+ ## Basic Cassandro
10
+
11
+ Connecting to Cassandra DB: `Cassandro.connect(Hash options)`. For full list of options visit [Ruby Driver Documentation](http://datastax.github.io/ruby-driver/api/#cluster-class_method)
12
+
13
+ ```ruby
14
+ Cassandro.connect(
15
+ hosts: ['127.0.0.1'],
16
+ keyspace: 'some_keyspace'
17
+ )
18
+ ```
19
+
20
+ Creating a new keyspace. For full details of keyspace creation visit [CLI keyspace](http://www.datastax.com/documentation/cassandra/2.0/cassandra/reference/referenceStorage_r.html)
21
+
22
+ ```ruby
23
+ Cassandro.create_keyspace('new_keyspace', 'SimpleStrategy', 1)
24
+ ```
25
+
26
+ Select keyspace outside `#connect`
27
+
28
+ ```ruby
29
+ Cassandro.use('keyspace_name')
30
+ ```
31
+
32
+ Create table.
33
+ ```ruby
34
+ table = <<-TABLEDEF
35
+ CREATE TABLE IF NOT EXISTS table_name (
36
+ id UUID,
37
+ username VARCHAR,
38
+ crypted_password VARCHAR,
39
+ created_at TIMESTAMP,
40
+ updated_at TIMESTAMP,
41
+ PRIMARY KEY(id,username)
42
+ )
43
+ TABLEDEF
44
+
45
+ Cassandro.execute(table)
46
+ ```
47
+
48
+ Execute queries.
49
+ ```ruby
50
+ result = Cassandro.execute("SELECT * FROM table_name;")
51
+ ```
52
+
53
+ Using Driver directly.
54
+ ```ruby
55
+ statement = Cassandro.client.prepare("SELECT * FROM table_name WHERE colname = ?;")
56
+ result = Cassandro.client.execute(statement, id)
57
+ ```
58
+
59
+ ## Cassandro::Model
60
+
61
+ ### Creating model
62
+ Creating new model: make you class inherits form `Cassandro::Model`
63
+
64
+ ```ruby
65
+ class SomeModel < Cassandro::Model
66
+ end
67
+ ```
68
+
69
+ Specifying table name using the method `table(table_name)`:
70
+
71
+ ```ruby
72
+ class SomeModel < Cassandro::Model
73
+
74
+ table 'some_models'
75
+ end
76
+ ```
77
+
78
+ Adding attributes using the method `attribute(name, type, options)`:
79
+
80
+ ```ruby
81
+ class SomeModel < Cassandro::Model
82
+
83
+ attribute :id, :uuid
84
+ attribute :name, :text
85
+ end
86
+ ```
87
+
88
+ types: :uuid, :text, :integer, :float, :timestamp, :datetime
89
+
90
+ Setting the primary key using the method `primary_key(pk_name | Array)`:
91
+
92
+ ```ruby
93
+ class SomeModel < Cassandro::Model
94
+
95
+ attribute :id, :uuid
96
+ attribute :name, :text
97
+
98
+ primary_key :id
99
+
100
+ end
101
+
102
+ class SomeModel < Cassandro::Model
103
+
104
+ attribute :id, :uuid
105
+ attribute :name, :text
106
+
107
+ primary_key [:id,:name]
108
+
109
+ end
110
+ ```
111
+
112
+ Setting unique field using the method `unique(field | Array)`:
113
+
114
+ ```ruby
115
+ class SomeModel < Cassandro::Model
116
+
117
+ unique :name
118
+ end
119
+ ```
120
+
121
+ __A complete example__
122
+
123
+ ```ruby
124
+ class SomeModel < Cassandro::Model
125
+
126
+ table 'some_models'
127
+
128
+ attribute :id, :uuid
129
+ attribute :name, :text
130
+
131
+ primary_key [:id, :name]
132
+
133
+ unique :name
134
+ end
135
+ ```
136
+
137
+ ### Interacting
138
+
139
+ Creating a new row:
140
+
141
+ ```ruby
142
+ somemodel = SomeModel.create(name: 'DaModel')
143
+ => #<SomeModel:0x00000001e7a0a8
144
+ @attributes={:name=>"DaModel", :id=>"1534214c-0e0b-455c-95e8-13677f56d6e5"},
145
+ @errors={},
146
+ @persisted=true>
147
+
148
+ ```
149
+
150
+ Find row:
151
+
152
+ ```ruby
153
+ SomeModel[name: 'DaModel']
154
+ => #<SomeModel:0x00000001d27930
155
+ @attributes={:id=>1534214c-0e0b-455c-95e8-13677f56d6e5, :name=>"DaModel"},
156
+ @errors={},
157
+ @persisted=true>
158
+ ```
159
+
160
+ Checking errors:
161
+ ```ruby
162
+ somemodel = SomeModel.create(name: 'DaModel')
163
+ => #<SomeModel:0x00000001d68160
164
+ @attributes={:name=>"DaModel", :id=>"a723301b-b94b-4a4b-8d36-872055734ab5"},
165
+ @errors={:unique=>"somemodel_not_unique"},
166
+ @persisted=false>
167
+
168
+ somemodel.persisted?
169
+ => false
170
+
171
+ somemodel.errors
172
+ => {:unique=>"somemodel_not_unique"}
173
+ ```
174
+
175
+ ## TODO
176
+
177
+ * Migrations
178
+ * Support Index
179
+ * Better queries
180
+ * Better documentation
181
+
182
+ ## How to collaborate
183
+
184
+ If you find a bug or want to collaborate with the code, you can:
185
+
186
+ * Report issues trhough the issue tracker
187
+ * Fork the repository into your own account and submit a Pull Request
data/cassandro.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "cassandro"
5
+ s.version = "0.1.0"
6
+ s.summary = "Ruby ORM for Apache Cassandra"
7
+ s.license = "MIT"
8
+ s.description = "Lightweight Apache Cassandra ORM for Ruby"
9
+ s.authors = ["Lautaro Orazi", "Leonardo Mateo"]
10
+ s.email = ["orazile@gmail.com", "leonardomateo@gmail.com"]
11
+ s.homepage = "https://github.com/tarolandia/cassandro"
12
+ s.require_paths = ["lib"]
13
+ s.add_dependency "cassandra-driver", '>= 1.0.0.beta.3'
14
+ s.add_development_dependency "protest", '~> 0.5.3'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ end
data/lib/cassandro.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative './cassandro/core'
2
+ require_relative './cassandro/support/hash'
3
+ require_relative './cassandro/ext/soft_delete'
4
+ require_relative './cassandro/model'
@@ -0,0 +1,66 @@
1
+ require 'cassandra'
2
+
3
+ module Cassandro
4
+ @@cluster = nil
5
+ @@session = nil
6
+ @@tables = []
7
+
8
+ def self.cluster
9
+ @@cluster
10
+ end
11
+
12
+ def self.client
13
+ @@session
14
+ end
15
+
16
+ def self.tables
17
+ @@tables
18
+ end
19
+
20
+ def self.connect(options = {})
21
+ keyspace = options.delete(:keyspace)
22
+ @@cluster = Cassandra.connect(options)
23
+ @@session = @@cluster.connect(keyspace || nil)
24
+ end
25
+
26
+ def self.use(keyspace)
27
+ @@session.execute("USE #{keyspace}") if @session
28
+ end
29
+
30
+ def self.disconnect
31
+ @@cluster.close if @cluster
32
+ @@session = nil
33
+ end
34
+
35
+ def self.execute(cql_command)
36
+ @@session.execute(cql_command)
37
+ end
38
+
39
+ def self.create_keyspace(name, strategy = 'SimpleStrategy', replication_factor = 1)
40
+ keyspace_definition = <<-KSDEF
41
+ CREATE KEYSPACE IF NOT EXISTS #{name}
42
+ WITH replication = {
43
+ 'class': '#{strategy}',
44
+ 'replication_factor': #{replication_factor}
45
+ }
46
+ KSDEF
47
+ @@session.execute(keyspace_definition)
48
+ end
49
+
50
+ def self.truncate_table(table_name)
51
+ @@session.execute("TRUNCATE #{table_name}")
52
+ end
53
+
54
+ def self.register_table(table_def)
55
+ @@tables << table_def
56
+ end
57
+
58
+ def self.load_tables
59
+ @@tables.each do |table_definition|
60
+ queries = table_definition.split(";").map(&:strip)
61
+ queries.each do |query|
62
+ @@session.execute(query) unless query.empty?
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,24 @@
1
+ module Cassandro
2
+ module SoftDelete
3
+ def self.included(model)
4
+ model.attribute :deleted, :boolean
5
+ end
6
+
7
+ def destroy
8
+ update_attributes(deleted: true)
9
+ end
10
+
11
+ def restore
12
+ update_attributes(deleted: false)
13
+ end
14
+
15
+ def deleted?
16
+ !!deleted
17
+ end
18
+
19
+ def exists?
20
+ !deleted
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,318 @@
1
+ module Cassandro
2
+ class Model
3
+ class ModelException < StandardError; end
4
+
5
+ attr_reader :attributes
6
+
7
+ def initialize(attrs = {}, persisted = false)
8
+ @attributes = {}
9
+ @errors = {}
10
+ @persisted = persisted
11
+
12
+ attrs.each do |att, val|
13
+ case val.class.name
14
+ when "Set"
15
+ send(:"#{att}=", val.to_a)
16
+ when "Cassandra::Uuid"
17
+ send(:"#{att}=", val.to_s)
18
+ else
19
+ send(:"#{att}=", val)
20
+ end
21
+ end
22
+ end
23
+
24
+ def persisted?
25
+ @persisted
26
+ end
27
+
28
+ def errors
29
+ @errors
30
+ end
31
+
32
+ def clear_errors
33
+ @errors = {}
34
+ end
35
+
36
+ def valid?
37
+ self.class.pk.any? && @errors.empty?
38
+ end
39
+
40
+ def cast(attribute)
41
+ self.class.cast_as(attribute, @attributes[attribute])
42
+ end
43
+
44
+ def unique?
45
+ self.class.uniqueness_defined? ? self.class[@attributes.slice(*self.class.uniques)].nil? : true
46
+ end
47
+
48
+ def update_attributes(attrs = {})
49
+ attrs = attrs.inject({}) do |memo, (k,v)|
50
+ memo[k.to_sym] = (v.nil? || v.to_s.empty?) ? nil : v #TODO: fix for Set, Map
51
+ memo
52
+ end#symbolize keys for later merge
53
+
54
+ p_keys = []
55
+ fields = []
56
+
57
+ self.class.pk.flatten.each do |k|
58
+ p_keys << "#{k} = #{cast(k)}"
59
+ end
60
+
61
+ attrs.keys.each do |field|
62
+ fields << "#{field} = ?"
63
+ end
64
+
65
+ query = "UPDATE #{self.class.table_name} SET #{fields.join(", ")} "
66
+ query += "WHERE #{p_keys.join(" AND ")}"
67
+
68
+ begin
69
+ st = Cassandro.client.prepare(query)
70
+ Cassandro.client.execute(st, *native_attributes(attrs))
71
+ @attributes.merge!(attrs)
72
+ true
73
+ rescue Exception => e
74
+ @errors[:update_error] = e.message
75
+ false
76
+ end
77
+ end
78
+
79
+ def save(insert_check = false)
80
+ clear_errors
81
+
82
+ if self.class.casts[:id] == :uuid
83
+ @attributes[:id] ||= SecureRandom.uuid
84
+ end
85
+
86
+ self.class.pk.flatten.each do |key|
87
+ if @attributes[key].nil?
88
+ @errors[:primary_key] = "#{key.to_s}_cant_be_nil"
89
+ return false
90
+ end
91
+ end
92
+
93
+ if !persisted? && self.class.uniqueness_defined? && !unique?
94
+ @errors[:unique] = "#{self.class.to_s.downcase}_not_unique"
95
+ return false
96
+ end
97
+
98
+ st = self.statement_for(:insert, :insert_check => insert_check)
99
+
100
+ begin
101
+ r = Cassandro.client.execute(st, *self.native_attributes)
102
+ raise ModelException.new('not_applied') unless !insert_check || (insert_check && r.first["[applied]"])
103
+ @persisted = true
104
+ rescue => e
105
+ @attributes[:id] = nil if !persisted? && @attributes.has_key?(:id)
106
+ @errors[:save] = e.message
107
+ false
108
+ end
109
+ end
110
+
111
+ def destroy
112
+ query = <<-QUERY
113
+ DELETE FROM #{self.class.table_name}
114
+ WHERE #{self.class.pk.flatten.map { |k| "#{k.to_s} = #{self.class.cast_as(k, @attributes[k])}" }.join(' AND ')}
115
+ QUERY
116
+ Cassandro.execute(query)
117
+ end
118
+
119
+ def self.table(name)
120
+ self.table_name = name.to_s
121
+ end
122
+
123
+ def self.attribute(name, type = String, options = {})
124
+ attributes << name
125
+ casts[name] = type
126
+
127
+ define_method(name) do
128
+ @attributes[name]
129
+ end
130
+
131
+ define_method(:"#{name}=") do |value|
132
+ @attributes[name] = value
133
+ end
134
+ end
135
+
136
+ def self.primary_key(keys)
137
+ if keys.is_a?(Array)
138
+ pk.push(*keys)
139
+ else
140
+ pk << keys
141
+ end
142
+ end
143
+
144
+ def self.unique(keys)
145
+ if keys.is_a?(Array)
146
+ uniques.push(*keys)
147
+ else
148
+ uniques << keys
149
+ end
150
+ end
151
+
152
+ def self.[](value)
153
+ if value.is_a?(Hash)
154
+ where = "#{value.map { |k,v| "#{k.to_s} = #{cast_as(k, v)}" }.join(' AND ')} ALLOW FILTERING"
155
+ else
156
+ where = "#{partition_key} = #{cast_as(partition_key, value)}"
157
+ end
158
+
159
+ query = <<-QUERY
160
+ SELECT *
161
+ FROM #{table_name}
162
+ WHERE #{where}
163
+ QUERY
164
+
165
+ result = Cassandro.execute(query)
166
+ return nil unless result.any?
167
+
168
+ self.new(result.first, true)
169
+ end
170
+
171
+ def self.create(attrs = {})
172
+ model = new(attrs)
173
+ model.save(true)
174
+
175
+ model
176
+ end
177
+
178
+ def self.all
179
+ query = "SELECT * FROM #{self.table_name}"
180
+
181
+ rows = Cassandro.execute(query)
182
+ all = []
183
+ rows.each do |row|
184
+ all << new(row)
185
+ end
186
+ all
187
+ end
188
+
189
+ def self.where(key, value)
190
+ key = key.to_sym
191
+ results = []
192
+
193
+ query = "SELECT * FROM #{table_name} WHERE #{key} = ? ALLOW FILTERING"
194
+
195
+ st = Cassandro.client.prepare(query)
196
+ rows = Cassandro.client.execute(st, value)
197
+
198
+ rows.each do |result|
199
+ results << new(result)
200
+ end
201
+
202
+ results
203
+ end
204
+
205
+ def self.count(key, value)
206
+ key = key.to_sym
207
+ query = "SELECT count(*) FROM #{table_name} WHERE #{key} = ? ALLOW FILTERING"
208
+
209
+ st = Cassandro.client.prepare(query)
210
+ results = Cassandro.client.execute(st, value)
211
+
212
+ results.first["count"]
213
+ end
214
+
215
+ def self.destroy_all
216
+ begin
217
+ query = "TRUNCATE #{table_name}"
218
+ st = Cassandro.execute(query)
219
+ st.is_a? Cassandra::Client::VoidResult
220
+ rescue e
221
+ false
222
+ end
223
+ end
224
+
225
+ def self.query(where, *values)
226
+ query = "SELECT * FROM #{table_name} WHERE #{where} ALLOW FILTERING"
227
+ st = Cassandro.client.prepare(query)
228
+ Cassandro.client.execute(st, *values)
229
+ end
230
+
231
+ protected
232
+ def self.attributes
233
+ @attributes ||= []
234
+ end
235
+
236
+ def self.pk
237
+ @pk ||= []
238
+ end
239
+
240
+ def self.table_name
241
+ @table_name ||= name.downcase
242
+ end
243
+
244
+ def self.table_name=(name)
245
+ @table_name = name
246
+ end
247
+
248
+ def self.casts
249
+ @cast ||= {}
250
+ end
251
+
252
+ def self.uniques
253
+ @unique ||= []
254
+ end
255
+
256
+ def self.uniqueness_defined?
257
+ uniques.any?
258
+ end
259
+
260
+ def self.cast_as(key, value)
261
+ return "NULL" if value.nil?
262
+
263
+ case casts[key]
264
+ when :text
265
+ "'#{value}'"
266
+ when :int, :integer
267
+ value.to_i
268
+ when :datetime
269
+ value.to_time.to_i * 1000
270
+ else
271
+ "#{value}"
272
+ end
273
+ end
274
+
275
+ def self.partition_key
276
+ pk.first
277
+ end
278
+
279
+ def statement_for(operation, options = {})
280
+ case operation
281
+ when :insert
282
+ #INSERT will update if the record already exists http://www.datastax.com/documentation/cql/3.0/cql/cql_reference/insert_r.html
283
+ query = <<-QUERY
284
+ INSERT INTO #{self.class.table_name}(#{@attributes.keys.map { |x| x.to_s}.join(',')})
285
+ VALUES(#{@attributes.keys.map { |x| '?' }.join(",")})
286
+ #{options[:insert_check] ? 'IF NOT EXISTS' : ''}
287
+ QUERY
288
+ if @insert_statement.nil? ||
289
+ @insert_statement.metadata.count != @attributes.count
290
+ @insert_statement = Cassandro.client.prepare(query)
291
+ else
292
+ @insert_statement
293
+ end
294
+ end
295
+ end
296
+
297
+ def native_attributes(attrs = nil)
298
+ n_attrs = []
299
+ attrs ||= @attributes
300
+
301
+ attrs.each do |k, v|
302
+ case self.class.casts[k]
303
+ when :uuid
304
+ n_attrs << Cassandra::Uuid.new(attrs[k])
305
+ when :integer
306
+ n_attrs << attrs[k].to_i
307
+ when :float
308
+ n_attrs << attrs[k].to_f
309
+ when :datetime
310
+ n_attrs << attrs[k].to_time.to_i
311
+ else
312
+ n_attrs << attrs[k]
313
+ end
314
+ end
315
+ n_attrs
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,29 @@
1
+ # Taken from i18n/core_ext/hash.rb ; https://github.com/svenfuchs/i18n/blob/master/lib/i18n/core_ext/hash.rb
2
+ class Hash
3
+ def slice(*keep_keys)
4
+ h = {}
5
+ keep_keys.each { |key| h[key] = fetch(key) }
6
+ h
7
+ end unless Hash.method_defined?(:slice)
8
+
9
+ def except(*less_keys)
10
+ slice(*keys - less_keys)
11
+ end unless Hash.method_defined?(:except)
12
+
13
+ def deep_symbolize_keys
14
+ inject({}) { |result, (key, value)|
15
+ value = value.deep_symbolize_keys if value.is_a?(Hash)
16
+ result[(key.to_sym rescue key) || key] = value
17
+ result
18
+ }
19
+ end unless Hash.method_defined?(:deep_symbolize_keys)
20
+
21
+ # deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
22
+ HASH_MERGER = proc do |key, v1, v2|
23
+ Hash === v1 && Hash === v2 ? v1.merge(v2, &HASH_MERGER) : v2
24
+ end
25
+
26
+ def deep_merge!(data)
27
+ merge!(data, &HASH_MERGER)
28
+ end unless Hash.method_defined?(:deep_merge!)
29
+ end
data/rakefile ADDED
@@ -0,0 +1,10 @@
1
+ task default: :test
2
+
3
+ desc "Run tests"
4
+ task :test do
5
+ load_files 'test/*_test.rb'
6
+ end
7
+
8
+ def load_files(dir)
9
+ Dir[dir].each { |file| load file }
10
+ end
@@ -0,0 +1,137 @@
1
+ require_relative 'helper'
2
+ require_relative 'support/tables'
3
+ require 'securerandom'
4
+
5
+ Protest.describe "Cassandro Model" do
6
+ setup do
7
+ Cassandro.connect(hosts: ['127.0.0.1'], keyspace: 'cassandro_test')
8
+ end
9
+
10
+ context 'Modeling' do
11
+ setup do
12
+ class Test < Cassandro::Model
13
+ end
14
+ end
15
+
16
+ test "adds table name" do
17
+ class Test < Cassandro::Model
18
+ table 'tests'
19
+ end
20
+ assert_equal 'tests', Test.table_name
21
+ end
22
+
23
+ test "adds attribute" do
24
+ class Test < Cassandro::Model
25
+ attribute :test_col_1, :uuid
26
+ attribute :test_col_2, :text
27
+ end
28
+ assert Test.attributes.include?(:test_col_1)
29
+ assert Test.attributes.include?(:test_col_2)
30
+ end
31
+
32
+ test "adds primary key" do
33
+ class Test < Cassandro::Model
34
+ primary_key :test_col_1
35
+ end
36
+ assert Test.pk.include?(:test_col_1)
37
+ end
38
+
39
+ test "adds unique field" do
40
+ class Test < Cassandro::Model
41
+ unique :test_col_1
42
+ end
43
+ assert Test.uniques.include?(:test_col_1)
44
+ assert Test.uniqueness_defined?
45
+ end
46
+
47
+ test "allows setting and getting attributes" do
48
+ uuid = SecureRandom.uuid
49
+ test = Test.new(test_col_1: uuid, test_col_2: 'test_value_2')
50
+ assert_equal uuid, test.test_col_1
51
+ assert_equal 'test_value_2', test.test_col_2
52
+ end
53
+ end
54
+
55
+ context 'Creating' do
56
+ setup do
57
+ class Test < Cassandro::Model
58
+ table 'tests'
59
+
60
+ attribute :test_col_1, :uuid
61
+ attribute :test_col_2, :text
62
+
63
+ primary_key :test_col_1
64
+ unique :test_col_1
65
+ end
66
+
67
+ Cassandro.truncate_table('tests')
68
+ end
69
+
70
+ test "creates a row" do
71
+ test = Test.create(test_col_1: SecureRandom.uuid, test_col_2: 'test_value_2')
72
+ assert test.persisted?
73
+ end
74
+
75
+ test "fails creating dup row" do
76
+ uuid = SecureRandom.uuid
77
+ test_1 = Test.create(test_col_1: uuid, test_col_2: 'test_value_2')
78
+ test_2 = Test.create(test_col_1: uuid, test_col_2: 'test_value_2')
79
+ assert !test_2.persisted?
80
+ assert_equal "test_not_unique", test_2.errors[:unique]
81
+ end
82
+ end
83
+
84
+ context 'Querying' do
85
+ setup do
86
+ class Test < Cassandro::Model
87
+ table 'tests'
88
+
89
+ attribute :test_col_1, :uuid
90
+ attribute :test_col_2, :text
91
+
92
+ primary_key :test_col_1
93
+ unique :test_col_1
94
+ end
95
+
96
+ Cassandro.truncate_table('tests')
97
+ end
98
+
99
+ test "gets row" do
100
+ uuid = SecureRandom.uuid
101
+ Test.create(test_col_1: uuid, test_col_2: 'test_value_2')
102
+ test = Test[uuid]
103
+ assert_equal uuid, test.test_col_1.to_s
104
+ assert_equal "test_value_2", test.test_col_2
105
+ end
106
+ end
107
+
108
+ context 'Updating' do
109
+ setup do
110
+ class Patient < Cassandro::Model
111
+ table :patients
112
+ attribute :name, :text
113
+ attribute :address, :text
114
+ attribute :age, :int
115
+
116
+ primary_key :name
117
+ end
118
+ Cassandro.truncate_table('patients')
119
+ end
120
+
121
+ test "updates attributes" do
122
+ patient = Patient.create(:name => "John Doe", :address => "Somewhere", :age => 30)
123
+ assert_equal 1, Patient.all.count
124
+
125
+ assert patient.update_attributes(:address => "Somewhere Else")
126
+ assert_equal 1, Patient.all.count
127
+ assert_equal "Somewhere Else", patient.address
128
+ end
129
+
130
+ test "won't update primary keys" do
131
+ patient = Patient.create(:name => "John Doe", :address => "Somewhere", :age => 30)
132
+
133
+ assert !patient.update_attributes(:name => "Jane Doe")
134
+ assert_equal "PRIMARY KEY part name found in SET part", patient.errors[:update_error]
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'helper'
2
+
3
+ Protest.describe "Cassandro Module" do
4
+ setup do
5
+ SESSION.execute("DROP KEYSPACE IF EXISTS test_keyspace")
6
+ end
7
+
8
+ test "connects to database" do
9
+ client = Cassandro.connect(hosts: ["127.0.0.1"])
10
+ assert_equal Cassandra::Session, client.class
11
+ end
12
+
13
+ test "creates new keyspace" do
14
+ Cassandro.connect(hosts: ["127.0.0.1"])
15
+ assert_equal Cassandra::Results::Void, Cassandro.create_keyspace("test_keyspace").class
16
+ assert_equal NilClass, Cassandro.use("test_keyspace").class
17
+ end
18
+ end
19
+
data/test/helper.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'rack/test'
2
+ require 'protest'
3
+
4
+ require_relative '../lib/cassandro'
5
+
6
+ CASSANDRA = Cassandra.connect(hosts: ['127.0.0.1'])
7
+ SESSION = CASSANDRA.connect
8
+
9
+ keyspace_definition = <<-KSDEF
10
+ CREATE KEYSPACE IF NOT EXISTS cassandro_test
11
+ WITH replication = {
12
+ 'class': 'SimpleStrategy',
13
+ 'replication_factor': 1
14
+ }
15
+ KSDEF
16
+
17
+ SESSION.execute(keyspace_definition)
18
+ SESSION.execute("USE cassandro_test")
19
+
20
+ class Protest::TestCase
21
+ include Rack::Test::Methods
22
+ end
23
+
24
+ Protest.report_with(:turn)
@@ -0,0 +1,20 @@
1
+ SESSION.execute("DROP TABLE IF EXISTS tests")
2
+ table = <<-TABLEDEF
3
+ CREATE TABLE IF NOT EXISTS tests (
4
+ test_col_1 UUID,
5
+ test_col_2 VARCHAR,
6
+ PRIMARY KEY(test_col_1)
7
+ )
8
+ TABLEDEF
9
+ SESSION.execute(table)
10
+
11
+ SESSION.execute("DROP TABLE IF EXISTS patients")
12
+ table = <<-TABLEDEF
13
+ CREATE TABLE IF NOT EXISTS patients (
14
+ name VARCHAR,
15
+ address VARCHAR,
16
+ age INT,
17
+ PRIMARY KEY(name)
18
+ )
19
+ TABLEDEF
20
+ SESSION.execute(table)
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cassandro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lautaro Orazi
8
+ - Leonardo Mateo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-10-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cassandra-driver
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: 1.0.0.beta.3
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ! '>='
26
+ - !ruby/object:Gem::Version
27
+ version: 1.0.0.beta.3
28
+ - !ruby/object:Gem::Dependency
29
+ name: protest
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 0.5.3
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: 0.5.3
42
+ description: Lightweight Apache Cassandra ORM for Ruby
43
+ email:
44
+ - orazile@gmail.com
45
+ - leonardomateo@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - .gems
51
+ - .gems-test
52
+ - .gitignore
53
+ - LICENSE
54
+ - README.md
55
+ - cassandro.gemspec
56
+ - lib/cassandro.rb
57
+ - lib/cassandro/core.rb
58
+ - lib/cassandro/ext/soft_delete.rb
59
+ - lib/cassandro/model.rb
60
+ - lib/cassandro/support/hash.rb
61
+ - rakefile
62
+ - test/cassandro_model_test.rb
63
+ - test/cassandro_test.rb
64
+ - test/helper.rb
65
+ - test/support/tables.rb
66
+ homepage: https://github.com/tarolandia/cassandro
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.4.2
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Ruby ORM for Apache Cassandra
90
+ test_files: []
91
+ has_rdoc: