cassandro 1.2.0 → 2.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.
- checksums.yaml +4 -4
- data/.gems +1 -1
- data/{LICENSE → LICENSE.md} +0 -0
- data/README.md +30 -303
- data/cassandro.gemspec +2 -2
- data/docs/advanced_features.md +59 -0
- data/docs/getting_started.md +70 -0
- data/docs/migrations.md +60 -0
- data/docs/modeling.md +93 -0
- data/docs/querying.md +120 -0
- data/lib/cassandro/core.rb +9 -0
- data/lib/cassandro/model.rb +53 -22
- data/test/cassandro_model_test.rb +29 -1
- data/test/support/tables.rb +3 -0
- metadata +12 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ca78baede197271950dd4171393ec32c9f3698f
|
4
|
+
data.tar.gz: 59a76cb0577d8b8293a1925d5ca071c889d61cbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b0810673c9631f7e60e0e9b7e4796b579c4c1b8b943f76f7dc0890a631f2b5fd0fbd08ca507d0abfaad198697206167ef6fcd84451123b759ca52d96abd6c2e
|
7
|
+
data.tar.gz: a5f4d40a976c593e3e6b149cf28573955b5e60381c24d2c4b8a04a92afcba110e3787784db5c127600bac5e9da7335a72c6ba0be7db021d8a8792509139f84b5
|
data/.gems
CHANGED
@@ -1 +1 @@
|
|
1
|
-
cassandra-driver -v 1.
|
1
|
+
cassandra-driver -v 2.1.3
|
data/{LICENSE → LICENSE.md}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,329 +1,56 @@
|
|
1
1
|
# Cassandro [](http://badge.fury.io/rb/cassandro)
|
2
2
|
|
3
|
-
Cassandro is a small Ruby ORM for Apache Cassandra 2.0 and CQL 3.0. Cassandro uses the new Datastax Ruby Driver
|
3
|
+
Cassandro is a small Ruby ORM for Apache Cassandra 2.0 and CQL 3.0. Cassandro uses the new [Datastax Ruby Driver](https://github.com/datastax/ruby-driver)
|
4
4
|
|
5
5
|
## Install
|
6
6
|
|
7
7
|
`gem install cassandro`
|
8
8
|
|
9
|
-
##
|
9
|
+
## Changelog
|
10
10
|
|
11
|
-
|
11
|
+
### v2.0
|
12
|
+
* Support `cassandra-driver` >= 2.0
|
13
|
+
* Allow registering indexes in model's definition
|
14
|
+
* Add `Model#ttl` method
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
# < 1.0.0
|
24
|
-
Cassandro.create_keyspace('new_keyspace', 'SimpleStrategy', 1)
|
25
|
-
|
26
|
-
# >= 1.0.0, allow more options
|
27
|
-
Cassandro.create_keyspace('new_keyspace', {
|
28
|
-
replication: {
|
29
|
-
class: 'NetworkTopologyStrategy',
|
30
|
-
dc_1: 2,
|
31
|
-
dc_2: 2
|
32
|
-
},
|
33
|
-
durable_writes: true
|
34
|
-
})
|
35
|
-
```
|
36
|
-
|
37
|
-
Select keyspace outside `#connect`
|
38
|
-
|
39
|
-
```ruby
|
40
|
-
Cassandro.use('keyspace_name')
|
41
|
-
```
|
42
|
-
|
43
|
-
Create table.
|
44
|
-
```ruby
|
45
|
-
table = <<-TABLEDEF
|
46
|
-
CREATE TABLE IF NOT EXISTS users (
|
47
|
-
email VARCHAR,
|
48
|
-
first_name VARCHAR,
|
49
|
-
age INT,
|
50
|
-
created_at TIMESTAMP,
|
51
|
-
PRIMARY KEY(email,created_at)
|
52
|
-
)
|
53
|
-
TABLEDEF
|
54
|
-
|
55
|
-
Cassandro.execute(table)
|
56
|
-
```
|
57
|
-
|
58
|
-
Execute queries.
|
59
|
-
```ruby
|
60
|
-
result = Cassandro.execute("SELECT * FROM table_name;")
|
61
|
-
```
|
62
|
-
|
63
|
-
Using Driver directly.
|
64
|
-
```ruby
|
65
|
-
statement = Cassandro.client.prepare("SELECT * FROM table_name WHERE colname = ?;")
|
66
|
-
result = Cassandro.client.execute(statement, id)
|
67
|
-
```
|
68
|
-
|
69
|
-
## Cassandro::Model
|
70
|
-
|
71
|
-
### Creating model
|
72
|
-
Creating new model: make you class inherits form `Cassandro::Model`
|
73
|
-
|
74
|
-
```ruby
|
75
|
-
class User < Cassandro::Model
|
76
|
-
end
|
77
|
-
```
|
78
|
-
|
79
|
-
Specifying table name using the method `table(table_name)`:
|
80
|
-
|
81
|
-
```ruby
|
82
|
-
class User < Cassandro::Model
|
83
|
-
|
84
|
-
table 'users'
|
85
|
-
end
|
86
|
-
```
|
16
|
+
### v1.2
|
17
|
+
* TTL
|
18
|
+
* Model-wide TTL
|
19
|
+
* Single record TTL
|
20
|
+
* Support `:set` datatype
|
21
|
+
* Ignore columns not definied on model
|
87
22
|
|
88
|
-
|
23
|
+
## Example
|
89
24
|
|
90
25
|
```ruby
|
91
|
-
class
|
92
|
-
|
93
|
-
|
94
|
-
attribute :first_name, :text
|
95
|
-
attribute :age, :integer
|
96
|
-
attribute :created_at, :datetime
|
97
|
-
end
|
98
|
-
```
|
99
|
-
|
100
|
-
types: :uuid, :text, :integer, :float, :timestamp, :datetime
|
101
|
-
|
102
|
-
Setting the primary key using the method `primary_key(pk_name | Array)`:
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
class User < Cassandro::Model
|
106
|
-
|
107
|
-
primary_key [:email, :created_at]
|
108
|
-
|
109
|
-
end
|
110
|
-
|
111
|
-
```
|
112
|
-
|
113
|
-
Setting unique field using the method `unique(field | Array)`:
|
114
|
-
|
115
|
-
```ruby
|
116
|
-
class User < Cassandro::Model
|
117
|
-
|
118
|
-
unique :email
|
119
|
-
end
|
120
|
-
```
|
121
|
-
|
122
|
-
|
123
|
-
### Setting TTL to a model
|
124
|
-
|
125
|
-
```Ruby
|
126
|
-
class Person < Cassandro::Model
|
127
|
-
table :people
|
128
|
-
ttl 60
|
129
|
-
end
|
130
|
-
```
|
131
|
-
This will make all the instances of the People class will have a Time To Live of `60` seconds in the database.
|
132
|
-
|
133
|
-
Creating a single record with a given TTL:
|
134
|
-
|
135
|
-
```Ruby
|
136
|
-
class Person < Cassandro::Model
|
137
|
-
table :people
|
138
|
-
attribute :first_name, :text
|
139
|
-
attribute :last_name, :text
|
140
|
-
end
|
141
|
-
|
142
|
-
Person.create_with_ttl(20, :first_name => "Eddie", :last_name => "Vedder")
|
143
|
-
```
|
144
|
-
|
145
|
-
This will create a record in the `people` table with a TTL of `20` seconds. It doesn't matter if the model has a different TTL set, this will override that TTL for _this record only_
|
146
|
-
|
147
|
-
|
148
|
-
__A complete example__
|
149
|
-
|
150
|
-
```ruby
|
151
|
-
class User < Cassandro::Model
|
152
|
-
|
153
|
-
table 'users'
|
154
|
-
|
26
|
+
class Developer < Cassandro::Model
|
27
|
+
table :developers
|
28
|
+
|
155
29
|
attribute :email, :text
|
156
|
-
attribute :
|
157
|
-
attribute :
|
158
|
-
attribute :created_at, :datetime
|
30
|
+
attribute :repos, :integer
|
31
|
+
attribute :nickname, :text
|
159
32
|
|
160
|
-
primary_key [:
|
161
|
-
|
162
|
-
|
163
|
-
end
|
164
|
-
```
|
165
|
-
|
166
|
-
### Interacting
|
167
|
-
|
168
|
-
Creating a new row:
|
169
|
-
|
170
|
-
```ruby
|
171
|
-
user = User.create(email: 'test1@example.com', first_name: 'Test', age: 30, created_at: DateTime.now)
|
172
|
-
=> #<User:0x00000001b9dc40
|
173
|
-
@attributes=
|
174
|
-
{:email=>"test1@example.com",
|
175
|
-
:first_name=>"Test",
|
176
|
-
:age=>30,
|
177
|
-
:created_at=>
|
178
|
-
#<DateTime: 2014-11-03T11:34:47-03:00 ((2456965j,52487s,201385585n),-10800s,2299161j)>},
|
179
|
-
@errors={},
|
180
|
-
@insert_statement=
|
181
|
-
#<Cassandra::Statements::Prepared:0xdcd4f8 @cql=" INSERT INTO users(email,first_name,age,created_at)\n VALUES(?,?,?,?)\n IF NOT EXISTS\n">,
|
182
|
-
@persisted=true>
|
183
|
-
|
184
|
-
```
|
185
|
-
|
186
|
-
Find:
|
187
|
-
|
188
|
-
```ruby
|
189
|
-
User[email: 'test1@example.com']
|
190
|
-
=> #<User:0x00000001cc59d8
|
191
|
-
@attributes=
|
192
|
-
{:email=>"test1@example.com",
|
193
|
-
:created_at=>2014-11-03 11:34:47 -0300,
|
194
|
-
:age=>30,
|
195
|
-
:first_name=>"Test"},
|
196
|
-
@errors={},
|
197
|
-
@persisted=true>
|
198
|
-
|
199
|
-
User.where('email','test1@example.com')
|
200
|
-
=> #<User:0x00000001cc59d8
|
201
|
-
@attributes=
|
202
|
-
{:email=>"test1@example.com",
|
203
|
-
:created_at=>2014-11-03 11:34:47 -0300,
|
204
|
-
:age=>30,
|
205
|
-
:first_name=>"Test"},
|
206
|
-
@errors={},
|
207
|
-
@persisted=true>
|
208
|
-
|
209
|
-
```
|
210
|
-
|
211
|
-
```ruby
|
212
|
-
User.all
|
213
|
-
=> [#<User:0x00000002bc75f8
|
214
|
-
@attributes=
|
215
|
-
{:email=>"test@example.com",
|
216
|
-
:created_at=>2014-11-03 11:30:52 -0300,
|
217
|
-
:age=>30,
|
218
|
-
:first_name=>"Test"},
|
219
|
-
@errors={},
|
220
|
-
@persisted=true>,
|
221
|
-
#<User:0x00000002bc6b30
|
222
|
-
@attributes=
|
223
|
-
{:email=>"test1@example.com",
|
224
|
-
:created_at=>2014-11-03 11:34:47 -0300,
|
225
|
-
:age=>30,
|
226
|
-
:first_name=>"Test"},
|
227
|
-
@errors={},
|
228
|
-
@persisted=true>]
|
229
|
-
```
|
230
|
-
|
231
|
-
```ruby
|
232
|
-
User.query('created_at > ?', Time.now.to_i)
|
233
|
-
=> #<Cassandra::Result:0x1fcb254 @rows=[{"email"=>"test@example.com", "created_at"=>2014-11-03 11:30:52 -0300, "age"=>30, "first_name"=>"Test"}, {"email"=>"test1@example.com", "created_at"=>2014-11-03 11:34:47 -0300, "age"=>30, "first_name"=>"Test"}] @last_page=true>
|
234
|
-
```
|
235
|
-
|
236
|
-
Count:
|
237
|
-
|
238
|
-
```ruby
|
239
|
-
User.count('email', 'test@example.com')
|
240
|
-
=> 1
|
241
|
-
```
|
242
|
-
|
243
|
-
Checking errors:
|
244
|
-
```ruby
|
245
|
-
user = User.create(email: 'test1@example.com', first_name: 'Test', age: 30, created_at: DateTime.now)
|
246
|
-
=> #<User:0x00000001dc7a48
|
247
|
-
@attributes=
|
248
|
-
{:email=>"test1@example.com",
|
249
|
-
:first_name=>"Test",
|
250
|
-
:age=>30,
|
251
|
-
:created_at=>
|
252
|
-
#<DateTime: 2014-11-03T11:36:40-03:00 ((2456965j,52600s,972972939n),-10800s,2299161j)>},
|
253
|
-
@errors={:unique=>"user_not_unique"},
|
254
|
-
@persisted=false>
|
255
|
-
|
256
|
-
user.persisted?
|
257
|
-
=> false
|
258
|
-
user.errors
|
259
|
-
=> {:unique=>"user_not_unique"}
|
260
|
-
|
261
|
-
```
|
262
|
-
|
263
|
-
## Migrations
|
264
|
-
|
265
|
-
Define your migrations by extending from `Cassandro::Migration`
|
266
|
-
|
267
|
-
```ruby
|
268
|
-
class UserMigration < Cassandro::Migration
|
269
|
-
version 1
|
270
|
-
|
271
|
-
def up
|
272
|
-
execute <<-TABLEDEF
|
273
|
-
CREATE TABLE users (
|
274
|
-
id UUID,
|
275
|
-
first_name VARCHAR,
|
276
|
-
last_name VARCHAR,
|
277
|
-
email VARCHAR,
|
278
|
-
PRIMARY KEY(id, email)
|
279
|
-
)
|
280
|
-
TABLEDEF
|
281
|
-
end
|
282
|
-
|
283
|
-
def down
|
284
|
-
execute <<-QUERY
|
285
|
-
DROP TABLE users;
|
286
|
-
QUERY
|
287
|
-
end
|
33
|
+
primary_key [:id, :repos]
|
34
|
+
|
35
|
+
index :nickname
|
288
36
|
end
|
289
37
|
|
290
|
-
|
291
|
-
|
292
|
-
version 2
|
38
|
+
Cassandro.connect(hosts: ['127.0.0.1'], keyspace: 'little_cassandro')
|
293
39
|
|
294
|
-
|
295
|
-
execute <<-TABLEUPDATE
|
296
|
-
ALTER TABLE users ADD gender VARCHAR
|
297
|
-
TABLEUPDATE
|
298
|
-
end
|
299
|
-
|
300
|
-
def down
|
301
|
-
execute <<-QUERY
|
302
|
-
ALTER TABLE users DROP gender
|
303
|
-
QUERY
|
304
|
-
end
|
305
|
-
end
|
40
|
+
Developer.create(email: 'developer@dev.com', repos: 10, nickname: 'cassandro')
|
306
41
|
```
|
307
42
|
|
308
|
-
|
309
|
-
|
310
|
-
```ruby
|
311
|
-
Cassandro.connect(hosts: ['127.0.0.1'], keyspace: 'some_keyspace')
|
43
|
+
## Documentation
|
312
44
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
migrator.migrate!(:up, 2) #migrates up to version 2
|
319
|
-
migrator.migrate!(:down, 1) #migrates down to version 1
|
320
|
-
```
|
45
|
+
* [Getting Started](docs/getting_started.md)
|
46
|
+
* [Migrations](docs/migrations.md)
|
47
|
+
* [Modeling](docs/modeling.md)
|
48
|
+
* [Querying](docs/querying.md)
|
49
|
+
* [Advanced Features](docs/advanced_features.md)
|
321
50
|
|
322
51
|
## TODO
|
323
52
|
|
324
|
-
*
|
325
|
-
* Better queries
|
326
|
-
* Better documentation
|
53
|
+
* Improve querying
|
327
54
|
|
328
55
|
## How to collaborate
|
329
56
|
|
data/cassandro.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "cassandro"
|
5
|
-
s.version = "
|
5
|
+
s.version = "2.0.0"
|
6
6
|
s.summary = "Ruby ORM for Apache Cassandra"
|
7
7
|
s.license = "MIT"
|
8
8
|
s.description = "Lightweight Apache Cassandra ORM for Ruby"
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = ["orazile@gmail.com", "leonardomateo@gmail.com"]
|
11
11
|
s.homepage = "https://github.com/tarolandia/cassandro"
|
12
12
|
s.require_paths = ["lib"]
|
13
|
-
s.add_dependency "cassandra-driver", '~> 1.
|
13
|
+
s.add_dependency "cassandra-driver", '~> 2.1.3', '>= 2.0.0'
|
14
14
|
s.add_development_dependency "protest", '~> 0.5', '>= 0.5.3'
|
15
15
|
s.add_development_dependency "rack-test", '~> 0.6', '>= 0.6.3'
|
16
16
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
## TTL
|
2
|
+
|
3
|
+
### Setting TTL to a model
|
4
|
+
|
5
|
+
```Ruby
|
6
|
+
class Person < Cassandro::Model
|
7
|
+
table :people
|
8
|
+
ttl 60
|
9
|
+
end
|
10
|
+
```
|
11
|
+
This will make all the instances of the People class will have a Time To Live of `60` seconds in the database.
|
12
|
+
|
13
|
+
### Creating a single record with a given TTL:
|
14
|
+
|
15
|
+
```Ruby
|
16
|
+
class Person < Cassandro::Model
|
17
|
+
table :people
|
18
|
+
attribute :first_name, :text
|
19
|
+
attribute :last_name, :text
|
20
|
+
end
|
21
|
+
|
22
|
+
Person.create_with_ttl(20, :first_name => "Eddie", :last_name => "Vedder")
|
23
|
+
```
|
24
|
+
|
25
|
+
This will create a record in the `people` table with a TTL of `20` seconds. It doesn't matter if the model has a different TTL set, this will override that TTL for _this record only_
|
26
|
+
|
27
|
+
### Getting TTL
|
28
|
+
|
29
|
+
After creating a record with a TTL you can use method `Model#ttl` to get the value.
|
30
|
+
|
31
|
+
```Ruby
|
32
|
+
person = Person.create_with_ttl(20, :first_name => "John", :last_name => "Lennon")
|
33
|
+
|
34
|
+
person.ttl # => 20
|
35
|
+
|
36
|
+
# ...
|
37
|
+
|
38
|
+
person.ttl # => 18
|
39
|
+
```
|
40
|
+
|
41
|
+
### Enabling Soft Delete
|
42
|
+
|
43
|
+
|
44
|
+
```Ruby
|
45
|
+
class Person < Cassandro::Model
|
46
|
+
include Cassandro::SoftDelete
|
47
|
+
|
48
|
+
table :people
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
This will add an attribute `:delete` to your model. Next time you use `Model#destroy` your data will not be deleted from database but marked as deleted. You can then use `Model#restore` to unmark it.
|
53
|
+
|
54
|
+
Data marked as deleted is not included within `Model#all` method by default. In order to include deleted records you have to send a boolean parameter to `all`.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
Person.all # => list all but deleted
|
58
|
+
Person.all(true) # => list all, deleted included
|
59
|
+
```
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Getting Started with Cassandro ORM
|
2
|
+
|
3
|
+
## Connection
|
4
|
+
|
5
|
+
Connecting to Cassandra DB: `Cassandro.connect(Hash options)`.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
# Connect
|
9
|
+
Cassandro.connect(hosts: ['192.168.2.100', '192.168.2.101'])
|
10
|
+
|
11
|
+
# Connect specifying keyspace
|
12
|
+
Cassandro.connect(hosts: ['192.168.2.100', '192.168.2.101'], keyspace: 'some_keyspace')
|
13
|
+
```
|
14
|
+
|
15
|
+
_For full list of options visit [Ruby Driver Documentation](http://datastax.github.io/ruby-driver/api/#cluster-class_method)_
|
16
|
+
|
17
|
+
## Keyspace
|
18
|
+
|
19
|
+
### Create Keyspace
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Cassandro.create_keyspace('new_keyspace', {
|
23
|
+
replication: {
|
24
|
+
class: 'NetworkTopologyStrategy',
|
25
|
+
dc_1: 2,
|
26
|
+
dc_2: 2
|
27
|
+
},
|
28
|
+
durable_writes: true
|
29
|
+
})
|
30
|
+
```
|
31
|
+
|
32
|
+
_For full details of keyspace creation visit [CLI keyspace](http://www.datastax.com/documentation/cassandra/2.0/cassandra/reference/referenceStorage_r.html)_
|
33
|
+
|
34
|
+
|
35
|
+
### Select keyspace
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
Cassandro.use('keyspace_name')
|
39
|
+
```
|
40
|
+
|
41
|
+
## Execute
|
42
|
+
|
43
|
+
### Execute queries
|
44
|
+
```ruby
|
45
|
+
result = Cassandro.execute("SELECT * FROM table_name;")
|
46
|
+
```
|
47
|
+
### Create table
|
48
|
+
```ruby
|
49
|
+
table = <<-TABLEDEF
|
50
|
+
CREATE TABLE IF NOT EXISTS users (
|
51
|
+
email VARCHAR,
|
52
|
+
first_name VARCHAR,
|
53
|
+
age INT,
|
54
|
+
created_at TIMESTAMP,
|
55
|
+
PRIMARY KEY(email,created_at)
|
56
|
+
)
|
57
|
+
TABLEDEF
|
58
|
+
|
59
|
+
Cassandro.execute(table)
|
60
|
+
```
|
61
|
+
|
62
|
+
## Cassandra Client
|
63
|
+
|
64
|
+
Cassandro provides access to `cassandra-driver` instance through `Cassandro.client`
|
65
|
+
|
66
|
+
### Using Driver directly
|
67
|
+
```ruby
|
68
|
+
statement = Cassandro.client.prepare("SELECT * FROM table_name WHERE colname = ?;")
|
69
|
+
result = Cassandro.client.execute(statement, id)
|
70
|
+
```
|
data/docs/migrations.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
## Migrations
|
2
|
+
|
3
|
+
Define your migrations by extending from `Cassandro::Migration`
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
class UserMigration < Cassandro::Migration
|
7
|
+
version 1
|
8
|
+
|
9
|
+
def up
|
10
|
+
execute <<-TABLEDEF
|
11
|
+
CREATE TABLE users (
|
12
|
+
id UUID,
|
13
|
+
first_name VARCHAR,
|
14
|
+
last_name VARCHAR,
|
15
|
+
email VARCHAR,
|
16
|
+
PRIMARY KEY(id, email)
|
17
|
+
)
|
18
|
+
TABLEDEF
|
19
|
+
end
|
20
|
+
|
21
|
+
def down
|
22
|
+
execute <<-QUERY
|
23
|
+
DROP TABLE users;
|
24
|
+
QUERY
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class UserGenderMigration < Cassandro::Migration
|
29
|
+
|
30
|
+
version 2
|
31
|
+
|
32
|
+
def up
|
33
|
+
execute <<-TABLEUPDATE
|
34
|
+
ALTER TABLE users ADD gender VARCHAR
|
35
|
+
TABLEUPDATE
|
36
|
+
end
|
37
|
+
|
38
|
+
def down
|
39
|
+
execute <<-QUERY
|
40
|
+
ALTER TABLE users DROP gender
|
41
|
+
QUERY
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Then use `Cassandro::Migrator` to run your migrations
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
Cassandro.connect(hosts: ['127.0.0.1'], keyspace: 'some_keyspace')
|
50
|
+
|
51
|
+
migrator = Cassandro::Migrator.new('./path/to/migrations', Logger.new(STDOUT))
|
52
|
+
|
53
|
+
migrator.migrate!(:up) #migrates to last version
|
54
|
+
migrator.migrate!(:down) #apply all downgrades
|
55
|
+
|
56
|
+
migrator.migrate!(:up, 2) #migrates up to version 2
|
57
|
+
migrator.migrate!(:down, 1) #migrates down to version 1
|
58
|
+
```
|
59
|
+
|
60
|
+
|
data/docs/modeling.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
## Cassandro::Model
|
2
|
+
|
3
|
+
### Creating model
|
4
|
+
|
5
|
+
#### Creating new model
|
6
|
+
|
7
|
+
Make you class inherits form `Cassandro::Model`
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class User < Cassandro::Model
|
11
|
+
end
|
12
|
+
```
|
13
|
+
#### Table name
|
14
|
+
|
15
|
+
Specify table name using the method `table(table_name)`:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
class User < Cassandro::Model
|
19
|
+
|
20
|
+
table 'users'
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
#### Attributes
|
25
|
+
|
26
|
+
Add attributes using the method `attribute(name, type, options)`:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class User < Cassandro::Model
|
30
|
+
|
31
|
+
attribute :email, :text
|
32
|
+
attribute :first_name, :text
|
33
|
+
attribute :age, :integer
|
34
|
+
attribute :created_at, :datetime
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
Types: `:uuid`, `:text`, `:integer`, `:float`, `:timestamp`, `:datetime`, `:set`
|
39
|
+
|
40
|
+
#### Primary Key
|
41
|
+
|
42
|
+
Set the primary key using the method `primary_key(pk_name | Array)`:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
class User < Cassandro::Model
|
46
|
+
|
47
|
+
primary_key [:email, :created_at]
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
```
|
52
|
+
|
53
|
+
#### Unique
|
54
|
+
|
55
|
+
Set unique fields using the method `unique(field | Array)`:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class User < Cassandro::Model
|
59
|
+
|
60
|
+
unique :email
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
#### Index
|
65
|
+
|
66
|
+
Set indexes using the method `index(field | Array)`. Note registering indexes in your model is only a refence.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
class User < Cassandro::Model
|
70
|
+
|
71
|
+
index :age
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
#### A complete example
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class User < Cassandro::Model
|
79
|
+
|
80
|
+
table 'users'
|
81
|
+
|
82
|
+
attribute :email, :text
|
83
|
+
attribute :first_name, :text
|
84
|
+
attribute :age, :integer
|
85
|
+
attribute :created_at, :datetime
|
86
|
+
|
87
|
+
primary_key [:email, :created_at]
|
88
|
+
|
89
|
+
unique :email
|
90
|
+
|
91
|
+
index :age
|
92
|
+
end
|
93
|
+
```
|
data/docs/querying.md
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
## Querying
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
class User < Cassandro::Model
|
5
|
+
table :users
|
6
|
+
|
7
|
+
attribute :email, :text
|
8
|
+
attribute :first_name, :text
|
9
|
+
attribute :age, :integer
|
10
|
+
attribute :created_at, :datetime
|
11
|
+
|
12
|
+
primary_key :email
|
13
|
+
|
14
|
+
index :first_name
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
### Creating a new row:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
user = User.create(email: 'test1@example.com', first_name: 'Test', age: 30, created_at: DateTime.now)
|
22
|
+
=> #<User:0x00000001b9dc40 ... @persisted=true>
|
23
|
+
|
24
|
+
```
|
25
|
+
|
26
|
+
### Updating attributes
|
27
|
+
|
28
|
+
#### Using `#save`
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
user.age = 31
|
32
|
+
user.save
|
33
|
+
=> true
|
34
|
+
```
|
35
|
+
|
36
|
+
#### Using `#update_attributes`
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
user.update_attributes(first_name: 'Test 1', age: 31)
|
40
|
+
=> true
|
41
|
+
```
|
42
|
+
|
43
|
+
### Selecting records
|
44
|
+
|
45
|
+
#### Find
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
User[email: 'test1@example.com']
|
49
|
+
=> #<User:0x00000001cc59d8 ... @persisted=true>
|
50
|
+
```
|
51
|
+
```ruby
|
52
|
+
User[email: 'test1@example.com', first_name: 'No Test']
|
53
|
+
=> nil
|
54
|
+
```
|
55
|
+
|
56
|
+
#### Find all
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
User.all
|
60
|
+
=> [#<User:0x00000001cc59d8 ... @persisted=true>, #<User:0x00000001cc59d9 ... @persisted=true>, ...]
|
61
|
+
```
|
62
|
+
#### Where / Query
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
User.where('first_name','Test 1')
|
66
|
+
=> [#<User:0x00000001cc59d8 ... @persisted=true>]
|
67
|
+
```
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
User.query('first_name = ?', 'Test')
|
71
|
+
=> [#<User:0x00000001cc59d8 ... @persisted=true>, ...]
|
72
|
+
```
|
73
|
+
|
74
|
+
#### Count:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
User.count('email', 'test@example.com')
|
78
|
+
=> 1
|
79
|
+
|
80
|
+
User.count
|
81
|
+
=> 2
|
82
|
+
```
|
83
|
+
|
84
|
+
### Deleting
|
85
|
+
|
86
|
+
#### Destroy
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
user = User[email: 'test1@example.com']
|
90
|
+
user.destroy
|
91
|
+
```
|
92
|
+
|
93
|
+
#### Destroy All (truncate table)
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
User.destroy_all
|
97
|
+
```
|
98
|
+
|
99
|
+
### Checking errors:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
user = User.create(email: 'test1@example.com', first_name: 'Test', age: 30, created_at: DateTime.now)
|
103
|
+
=> #<User:0x00000001dc7a48
|
104
|
+
@attributes=
|
105
|
+
{:email=>"test1@example.com",
|
106
|
+
:first_name=>"Test",
|
107
|
+
:age=>30,
|
108
|
+
:created_at=>
|
109
|
+
#<DateTime: 2014-11-03T11:36:40-03:00 ((2456965j,52600s,972972939n),-10800s,2299161j)>},
|
110
|
+
@errors={:unique=>"user_not_unique"},
|
111
|
+
@persisted=false>
|
112
|
+
|
113
|
+
user.persisted?
|
114
|
+
=> false
|
115
|
+
user.errors
|
116
|
+
=> {:unique=>"user_not_unique"}
|
117
|
+
|
118
|
+
```
|
119
|
+
|
120
|
+
|
data/lib/cassandro/core.rb
CHANGED
data/lib/cassandro/model.rb
CHANGED
@@ -70,7 +70,7 @@ module Cassandro
|
|
70
70
|
|
71
71
|
begin
|
72
72
|
st = Cassandro.client.prepare(query)
|
73
|
-
Cassandro.client.execute(st,
|
73
|
+
Cassandro.client.execute(st, arguments: native_attributes(attrs))
|
74
74
|
@attributes.merge!(attrs)
|
75
75
|
true
|
76
76
|
rescue Exception => e
|
@@ -101,7 +101,7 @@ module Cassandro
|
|
101
101
|
st = self.statement_for(:insert, :insert_check => insert_check)
|
102
102
|
|
103
103
|
begin
|
104
|
-
r = Cassandro.client.execute(st,
|
104
|
+
r = Cassandro.client.execute(st, arguments: self.native_attributes)
|
105
105
|
raise ModelException.new('not_applied') unless !insert_check || (insert_check && r.first["[applied]"])
|
106
106
|
@persisted = true
|
107
107
|
rescue => e
|
@@ -119,11 +119,21 @@ module Cassandro
|
|
119
119
|
Cassandro.execute(query)
|
120
120
|
end
|
121
121
|
|
122
|
+
def ttl
|
123
|
+
query = <<-QUERY
|
124
|
+
SELECT TTL(#{@attributes.keys.last}) FROM #{self.class.table_name}
|
125
|
+
WHERE #{self.class.pk.flatten.map { |k| "#{k.to_s} = #{self.class.cast_as(k, @attributes[k])}" }.join(' AND ')}
|
126
|
+
QUERY
|
127
|
+
result = Cassandro.execute(query)
|
128
|
+
result.any? ? result.first.values.first : nil
|
129
|
+
end
|
130
|
+
|
122
131
|
def self.table(name)
|
123
132
|
self.table_name = name.to_s
|
124
133
|
end
|
125
134
|
|
126
135
|
def self.attribute(name, type = String, options = {})
|
136
|
+
return if attributes.include?(name)
|
127
137
|
attributes << name
|
128
138
|
casts[name] = type
|
129
139
|
|
@@ -138,17 +148,25 @@ module Cassandro
|
|
138
148
|
|
139
149
|
def self.primary_key(keys)
|
140
150
|
if keys.is_a?(Array)
|
141
|
-
pk.
|
151
|
+
pk.merge(keys)
|
142
152
|
else
|
143
|
-
pk
|
153
|
+
pk.add(keys)
|
144
154
|
end
|
145
155
|
end
|
146
156
|
|
147
157
|
def self.unique(keys)
|
148
158
|
if keys.is_a?(Array)
|
149
|
-
uniques.
|
159
|
+
uniques.merge(keys)
|
150
160
|
else
|
151
|
-
uniques
|
161
|
+
uniques.add(keys)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.index(keys)
|
166
|
+
if keys.is_a?(Array)
|
167
|
+
indexes.merge(keys)
|
168
|
+
else
|
169
|
+
indexes.add(keys)
|
152
170
|
end
|
153
171
|
end
|
154
172
|
|
@@ -170,7 +188,7 @@ module Cassandro
|
|
170
188
|
QUERY
|
171
189
|
|
172
190
|
st = Cassandro.client.prepare(query)
|
173
|
-
result = Cassandro.client.execute(st,
|
191
|
+
result = Cassandro.client.execute(st, arguments: values)
|
174
192
|
|
175
193
|
return nil unless result.any?
|
176
194
|
|
@@ -184,6 +202,16 @@ module Cassandro
|
|
184
202
|
model
|
185
203
|
end
|
186
204
|
|
205
|
+
def self.create_with_ttl(seconds, attrs = {})
|
206
|
+
old_ttl = self.options[:ttl]
|
207
|
+
self.options[:ttl] = seconds.to_i
|
208
|
+
|
209
|
+
result = self.create(attrs)
|
210
|
+
|
211
|
+
old_ttl ? self.options[:ttl] = old_ttl : self.options.delete(:ttl)
|
212
|
+
result
|
213
|
+
end
|
214
|
+
|
187
215
|
def self.all
|
188
216
|
query = "SELECT * FROM #{self.table_name}"
|
189
217
|
|
@@ -202,7 +230,7 @@ module Cassandro
|
|
202
230
|
query = "SELECT * FROM #{table_name} WHERE #{key} = ? ALLOW FILTERING"
|
203
231
|
|
204
232
|
st = Cassandro.client.prepare(query)
|
205
|
-
rows = Cassandro.client.execute(st, value)
|
233
|
+
rows = Cassandro.client.execute(st, arguments: [value])
|
206
234
|
|
207
235
|
rows.each do |result|
|
208
236
|
results << new(result, true)
|
@@ -218,7 +246,7 @@ module Cassandro
|
|
218
246
|
key = key.to_sym
|
219
247
|
query << " WHERE #{key} = ? ALLOW FILTERING"
|
220
248
|
st = Cassandro.client.prepare(query)
|
221
|
-
results = Cassandro.client.execute(st, value)
|
249
|
+
results = Cassandro.client.execute(st, arguments: [value])
|
222
250
|
else
|
223
251
|
results = Cassandro.client.execute(query)
|
224
252
|
end
|
@@ -237,9 +265,18 @@ module Cassandro
|
|
237
265
|
end
|
238
266
|
|
239
267
|
def self.query(where, *values)
|
268
|
+
results = []
|
269
|
+
|
240
270
|
query = "SELECT * FROM #{table_name} WHERE #{where} ALLOW FILTERING"
|
241
271
|
st = Cassandro.client.prepare(query)
|
242
|
-
Cassandro.client.execute(st,
|
272
|
+
rows = Cassandro.client.execute(st, arguments: values)
|
273
|
+
|
274
|
+
|
275
|
+
rows.each do |result|
|
276
|
+
results << new(result, true)
|
277
|
+
end
|
278
|
+
|
279
|
+
results
|
243
280
|
end
|
244
281
|
|
245
282
|
protected
|
@@ -248,7 +285,7 @@ module Cassandro
|
|
248
285
|
end
|
249
286
|
|
250
287
|
def self.pk
|
251
|
-
@pk ||=
|
288
|
+
@pk ||= Set.new
|
252
289
|
end
|
253
290
|
|
254
291
|
def self.table_name
|
@@ -264,7 +301,11 @@ module Cassandro
|
|
264
301
|
end
|
265
302
|
|
266
303
|
def self.uniques
|
267
|
-
@unique ||=
|
304
|
+
@unique ||= Set.new
|
305
|
+
end
|
306
|
+
|
307
|
+
def self.indexes
|
308
|
+
@indexes ||= Set.new
|
268
309
|
end
|
269
310
|
|
270
311
|
def self.uniqueness_defined?
|
@@ -298,16 +339,6 @@ module Cassandro
|
|
298
339
|
self.options[:ttl] = seconds.to_i
|
299
340
|
end
|
300
341
|
|
301
|
-
def self.create_with_ttl(seconds, attrs = {})
|
302
|
-
old_ttl = self.options[:ttl]
|
303
|
-
self.options[:ttl] = seconds.to_i
|
304
|
-
|
305
|
-
result = self.create(attrs)
|
306
|
-
|
307
|
-
old_ttl ? self.options[:ttl] = old_ttl : self.options.delete(:ttl)
|
308
|
-
result
|
309
|
-
end
|
310
|
-
|
311
342
|
def statement_for(operation, options = {})
|
312
343
|
case operation
|
313
344
|
when :insert
|
@@ -44,6 +44,28 @@ Protest.describe "Cassandro Model" do
|
|
44
44
|
assert Test.uniqueness_defined?
|
45
45
|
end
|
46
46
|
|
47
|
+
test "adds index" do
|
48
|
+
class Test < Cassandro::Model
|
49
|
+
index :test_col_2
|
50
|
+
end
|
51
|
+
|
52
|
+
assert Test.indexes.include?(:test_col_2)
|
53
|
+
end
|
54
|
+
|
55
|
+
test "adds multiple indexes" do
|
56
|
+
class Test < Cassandro::Model
|
57
|
+
attribute :test_col_3, :text
|
58
|
+
attribute :test_col_4, :text
|
59
|
+
attribute :test_col_5, :text
|
60
|
+
|
61
|
+
index :test_col_3
|
62
|
+
index [:test_col_4, :test_col_5]
|
63
|
+
end
|
64
|
+
assert Test.indexes.include?(:test_col_3)
|
65
|
+
assert Test.indexes.include?(:test_col_4)
|
66
|
+
assert Test.indexes.include?(:test_col_5)
|
67
|
+
end
|
68
|
+
|
47
69
|
test "allows setting and getting attributes" do
|
48
70
|
uuid = SecureRandom.uuid
|
49
71
|
test = Test.new(test_col_1: uuid, test_col_2: 'test_value_2')
|
@@ -159,7 +181,7 @@ Protest.describe "Cassandro Model" do
|
|
159
181
|
Test.create(test_col_1: uuid, test_col_2: 'test_value_2')
|
160
182
|
|
161
183
|
tests = Test[uuid]
|
162
|
-
assert_equal false, tests.respond_to?("
|
184
|
+
assert_equal false, tests.respond_to?("test_col_ignored")
|
163
185
|
end
|
164
186
|
end
|
165
187
|
|
@@ -247,5 +269,11 @@ Protest.describe "Cassandro Model" do
|
|
247
269
|
assert results.first["ttl(address)"] > 0
|
248
270
|
assert results.first["ttl(address)"] <= 30
|
249
271
|
end
|
272
|
+
|
273
|
+
test "should get TTL" do
|
274
|
+
patient = Patient.create_with_ttl(20, :name => "Cassandro Get", :address => "cassandstreet", :age => 1)
|
275
|
+
assert patient.ttl > 0
|
276
|
+
assert patient.ttl <= 20
|
277
|
+
end
|
250
278
|
end
|
251
279
|
end
|
data/test/support/tables.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cassandro
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lautaro Orazi
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-04-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: cassandra-driver
|
@@ -17,20 +17,20 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
20
|
+
version: 2.1.3
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version:
|
23
|
+
version: 2.0.0
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
27
27
|
requirements:
|
28
28
|
- - "~>"
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version:
|
30
|
+
version: 2.1.3
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 2.0.0
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: protest
|
36
36
|
requirement: !ruby/object:Gem::Requirement
|
@@ -82,9 +82,14 @@ files:
|
|
82
82
|
- ".gems"
|
83
83
|
- ".gems-test"
|
84
84
|
- ".gitignore"
|
85
|
-
- LICENSE
|
85
|
+
- LICENSE.md
|
86
86
|
- README.md
|
87
87
|
- cassandro.gemspec
|
88
|
+
- docs/advanced_features.md
|
89
|
+
- docs/getting_started.md
|
90
|
+
- docs/migrations.md
|
91
|
+
- docs/modeling.md
|
92
|
+
- docs/querying.md
|
88
93
|
- lib/cassandro.rb
|
89
94
|
- lib/cassandro/core.rb
|
90
95
|
- lib/cassandro/ext/migration.rb
|