cassandra_record 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 99918f8fbbb1d222dea67d9eea38e577f4eac03f
4
- data.tar.gz: ebea7d93413a497267341b6aa2cf8a791295ff17
3
+ metadata.gz: 3f7f7196553b6334084a1b9cbdc32fcc741c7234
4
+ data.tar.gz: 0d387f3cbb15897d9f585c52d722a4fe31dd124b
5
5
  SHA512:
6
- metadata.gz: 00ff11ffd3d543b8067417bf4db87a35983f37240113a903cd452c12c1d76360576e49df96632051ec6d2b8d4edef6d26416ee9abc2a2237617a9e478b9c08ec
7
- data.tar.gz: 87bdf5aea30cac22b1f64dc96b632015877c57948b3527b6f4dbefb4f0c152723d5d6b62df59626fd06ff6738ddd5113482965a5b813b9b1b6e218993d7c8d8a
6
+ metadata.gz: c1b89fe70793c22999b40a738ef9ba09b868ffcd6fc91ea1099384a90de9214d5288068d83499a37aa5df73eb056626ab1c8cf1d4df43110eb8e40fe53b152e8
7
+ data.tar.gz: 0a28d9c85a751abbafb5642495bf751fca95e8beeb2a154bfb9399bc53067eec0e946bbefa0edd6113467160cadc6ef57c23d9f08ad35d57457dfc4d5ca1a410
data/Gemfile CHANGED
@@ -12,7 +12,3 @@ group :development, :test do
12
12
  gem 'pry-byebug'
13
13
  gem 'pry-doc'
14
14
  end
15
-
16
-
17
- gem 'activesupport'
18
- gem 'cassandra-driver'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cassandra_record (0.0.5)
4
+ cassandra_record (0.1.0)
5
5
  activesupport
6
6
  cassandra-driver
7
7
 
@@ -17,8 +17,8 @@ GEM
17
17
  byebug (2.7.0)
18
18
  columnize (~> 0.3)
19
19
  debugger-linecache (~> 1.2)
20
- cassandra-driver (1.0.0.rc.1)
21
- ione (~> 1.2.0.pre9)
20
+ cassandra-driver (2.1.0)
21
+ ione (~> 1.2)
22
22
  coderay (1.1.0)
23
23
  columnize (0.8.9)
24
24
  debugger-linecache (1.2.0)
@@ -61,9 +61,7 @@ PLATFORMS
61
61
  ruby
62
62
 
63
63
  DEPENDENCIES
64
- activesupport
65
64
  bundler (~> 1.7)
66
- cassandra-driver
67
65
  cassandra_record!
68
66
  pry
69
67
  pry-byebug
data/README.md CHANGED
@@ -4,20 +4,70 @@
4
4
 
5
5
  Add this line to your application's Gemfile:
6
6
 
7
- ```ruby
7
+ ```bash
8
8
  gem 'cassandra_record'
9
9
  ```
10
10
 
11
11
  And then execute:
12
12
 
13
- $ bundle
13
+ ```bash
14
+ $ bundle
15
+ ```
14
16
 
15
17
  Or install it yourself as:
16
18
 
17
- $ gem install cassandra_record
19
+ ```bash
20
+ $ gem install cassandra_record
21
+ ```
18
22
 
19
23
  ## Usage
20
24
 
25
+ CassandraRecord models are based on the following Cassandra table:
26
+
27
+ ```sql
28
+ CREATE TABLE thingies (
29
+ id int,
30
+ name text,
31
+ PRIMARY KEY (id)
32
+ );
33
+ ```
34
+
35
+ __A simple Cassandra-backed model__
36
+
37
+ Define the model by inheriting from CassandraRecord::Base. ...and you're done. ...you're welcome
38
+
39
+ ```ruby
40
+ class Thingy < CassandraRecord::Base
41
+ end
42
+
43
+ # record creation
44
+ Thingy.create(id: 123, name: 'pizza')
45
+
46
+ # record retrieval and attribute access
47
+ my_thingy = Thingy.where(id: 123)
48
+ my_thingy.name # => pizza
49
+ ```
50
+
51
+ __A model with creation options__
52
+
53
+ Override the instance-level #create method.
54
+ Overriding the instance-level #create method will apply the configured options to all created records.
55
+
56
+ ```ruby
57
+ class Thingy < CassandraRecord::Base
58
+ TTL = 3600 # one hour
59
+
60
+ def create
61
+ options = { ttl: TTL }
62
+ super(options)
63
+ end
64
+ end
65
+
66
+ # record creation
67
+ # this record will auto-expire in 1 hour.
68
+ Thingy.create(id: 123, name: 'spaghetti')
69
+ ```
70
+
21
71
  ## Contributing
22
72
 
23
73
  1. Fork it ( https://github.com/zephyr-dev/cassandra_record/fork )
@@ -25,3 +75,5 @@ Or install it yourself as:
25
75
  3. Commit your changes (`git commit -am 'Add some feature'`)
26
76
  4. Push to the branch (`git push origin my-new-feature`)
27
77
  5. Create a new Pull Request
78
+
79
+
@@ -5,4 +5,7 @@ require 'cassandra_record/statement'
5
5
  require 'cassandra_record/database/adapters/cassandra'
6
6
 
7
7
  module CassandraRecord
8
+ Base.configure do |configuration|
9
+ configuration.database_adapter = Database::Adapters::Cassandra.instance
10
+ end
8
11
  end
@@ -9,9 +9,28 @@ module CassandraRecord
9
9
  new(attributes).create
10
10
  end
11
11
 
12
+ def batch_create(array_of_attributes, options={})
13
+ batch = configuration.database_adapter.session.batch do |batch|
14
+ array_of_attributes.map do |attr|
15
+ batch.add(new(attr).send(:insert_statement, attr, options), attr.values)
16
+ end
17
+ end
18
+
19
+ configuration.database_adapter.session.execute(batch)
20
+ array_of_attributes.map { |attr| new(attr) }
21
+ end
22
+
12
23
  def where(attributes={})
13
24
  new.where(attributes)
14
25
  end
26
+
27
+ def configure
28
+ yield configuration
29
+ end
30
+
31
+ def configuration
32
+ @@configuration ||= Configuration.new
33
+ end
15
34
  end
16
35
 
17
36
  attr_accessor :attributes
@@ -21,28 +40,36 @@ module CassandraRecord
21
40
  end
22
41
 
23
42
  def where(options={})
24
- results = Statement.where(table_name, options).map do |attributes|
43
+ db.execute(where_statement(options)).map do |attributes|
25
44
  self.class.new(attributes)
26
45
  end
27
46
  end
28
47
 
29
- def create
30
- Statement.create(table_name, columns, values)
48
+ def create(options={})
49
+ db.execute(insert_statement(attributes, options), *attributes.values)
31
50
  self
32
51
  end
33
52
 
34
53
  private
35
54
 
36
- def table_name
37
- ActiveSupport::Inflector.tableize(self.class.name).gsub(/\//, '_')
55
+ def db
56
+ self.class.configuration.database_adapter
38
57
  end
39
58
 
40
- def columns
41
- attributes.keys
59
+ def where_statement(options={})
60
+ Statement.where(table_name, options)
42
61
  end
43
62
 
44
- def values
45
- attributes.values
63
+ def insert_statement(attributes, options={})
64
+ @insert_statement ||= db.prepare(insert_cql(attributes, options))
65
+ end
66
+
67
+ def insert_cql(attributes, options={})
68
+ Statement.create(table_name, attributes.keys, attributes.values, options)
69
+ end
70
+
71
+ def table_name
72
+ ActiveSupport::Inflector.tableize(self.class.name).gsub(/\//, '_')
46
73
  end
47
74
 
48
75
  def method_missing(method, *args, &block)
@@ -53,5 +80,13 @@ module CassandraRecord
53
80
  end
54
81
  end
55
82
 
83
+ class Configuration
84
+ attr_accessor :database_adapter
85
+
86
+ def initialize(adapter=Database::Adapters::Cassandra.instance)
87
+ adapter
88
+ end
89
+ end
90
+
56
91
  end
57
92
  end
@@ -16,11 +16,15 @@ module CassandraRecord
16
16
  end
17
17
 
18
18
  def prepare(cql)
19
- session.prepare(cql)
19
+ rescue_with_reset_and_retry do
20
+ session.prepare(cql)
21
+ end
20
22
  end
21
23
 
22
24
  def execute(cql, *args)
23
- session.execute(cql, *args)
25
+ rescue_with_reset_and_retry do
26
+ session.execute(cql, arguments: args)
27
+ end
24
28
  end
25
29
 
26
30
  def cluster
@@ -38,6 +42,27 @@ module CassandraRecord
38
42
 
39
43
  private
40
44
 
45
+ MAX_RETRIES = 4
46
+
47
+ def rescue_with_reset_and_retry
48
+ retry_count = 0
49
+ begin
50
+ yield
51
+ rescue ::Cassandra::Error
52
+ if (retry_count += 1) < MAX_RETRIES
53
+ reset_session
54
+ sleep(0.5)
55
+ retry
56
+ else
57
+ raise
58
+ end
59
+ end
60
+ end
61
+
62
+ def reset_session
63
+ @session = nil
64
+ end
65
+
41
66
  def cluster_connection
42
67
  ::Cassandra.cluster(connection_configuration.symbolize_keys)
43
68
  end
@@ -5,28 +5,28 @@ module CassandraRecord
5
5
  cql = base_where_query(table_name)
6
6
 
7
7
  if options.present?
8
- cql << 'WHERE'
8
+ cql << 'WHERE '
9
9
  cql << parse_where_clause_options(options)
10
10
  end
11
11
 
12
12
  cql << ';'
13
- db.execute(cql)
14
13
  end
15
14
 
16
- def create(table_name, columns, values)
15
+ def create(table_name, columns, values, options={})
17
16
  cql = <<-CQL
18
17
  INSERT INTO #{table_name} (#{columns.join(", ")})
19
18
  VALUES (#{value_placeholders(values).join(", ")})
20
19
  CQL
21
20
 
22
- insert_statement = db.prepare(cql)
23
- db.execute(insert_statement, *values)
21
+ cql.tap do |statement|
22
+ statement << ttl(options[:ttl]) if options.has_key?(:ttl)
23
+ end
24
24
  end
25
25
 
26
26
  private
27
27
 
28
- def db
29
- Database::Adapters::Cassandra.instance
28
+ def ttl(secs)
29
+ "USING TTL #{secs}"
30
30
  end
31
31
 
32
32
  def value_placeholders(values)
@@ -1,3 +1,3 @@
1
1
  module CassandraRecord
2
- VERSION = "0.0.5"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -4,6 +4,12 @@ describe CassandraRecord::Base do
4
4
  let(:db) { RSpec.configuration.db }
5
5
  let(:keyspace) { RSpec.configuration.keyspace }
6
6
 
7
+ before do
8
+ CassandraRecord::Base.configure do |configuration|
9
+ configuration.database_adapter = ::CassandraRecord::Database::Adapters::Cassandra.instance
10
+ end
11
+ end
12
+
7
13
  class TestRecord < CassandraRecord::Base; end
8
14
 
9
15
  describe ".create" do
@@ -18,8 +24,8 @@ describe CassandraRecord::Base do
18
24
  record = TestRecord.create(id: 99, name: 'turkey')
19
25
 
20
26
  select = <<-CQL
21
- SELECT * from #{keyspace}.test_records
22
- WHERE id = 99;
27
+ SELECT * from #{keyspace}.test_records
28
+ WHERE id = 99;
23
29
  CQL
24
30
 
25
31
  results = db.execute(select)
@@ -29,6 +35,72 @@ describe CassandraRecord::Base do
29
35
  expect(result['id']).to eq(99)
30
36
  expect(result['name']).to eq('turkey')
31
37
  end
38
+
39
+ context "with TTL options" do
40
+ class TestRecord < CassandraRecord::Base
41
+ def create
42
+ options = { ttl: 1 }
43
+ super(options)
44
+ end
45
+ end
46
+
47
+ it "persists a record" do
48
+ TestRecord.create(id: 300, name: 'I\'m going away')
49
+
50
+ select = <<-CQL
51
+ SELECT * from #{keyspace}.test_records
52
+ WHERE id = 300;
53
+ CQL
54
+
55
+ results = db.execute(select)
56
+ expect(results.count).to eq(1)
57
+
58
+ # sucky, but we need to wait for Cassandra
59
+ # to remove the record to assert the TTL is working
60
+ sleep 1
61
+
62
+ results = db.execute(select)
63
+ expect(results.count).to eq(0)
64
+ end
65
+ end
66
+ end
67
+
68
+ describe ".batch_create" do
69
+ context "with TTL options" do
70
+ it "persists a record" do
71
+ TestRecord.batch_create([{ id: 99, name: 'turkey' }, { id: 100, name: 'buffalo' }], ttl: 1)
72
+
73
+ select = <<-CQL
74
+ SELECT * from #{keyspace}.test_records
75
+ CQL
76
+
77
+ results = db.execute(select)
78
+ expect(results.count).to eq 2
79
+
80
+ sleep 2
81
+
82
+ results = db.execute(select)
83
+ expect(results.count).to eq 0
84
+ end
85
+ end
86
+
87
+ it "returns an array of the records it created" do
88
+ records = TestRecord.batch_create([{ id: 99, name: 'turkey' }, { id: 100, name: 'buffalo' }])
89
+ expect(records.first.name).to eq "turkey"
90
+ expect(records.last.name).to eq "buffalo"
91
+ end
92
+
93
+ it "persists the records" do
94
+ TestRecord.batch_create([{ id: 99, name: 'turkey' }, { id: 100, name: 'buffalo' }])
95
+
96
+ select = <<-CQL
97
+ SELECT * from #{keyspace}.test_records
98
+ CQL
99
+
100
+ results = db.execute(select)
101
+ expect(results.count).to eq 2
102
+ expect(results.first['id']).to eq 99
103
+ end
32
104
  end
33
105
 
34
106
  describe ".where" do
@@ -72,4 +144,34 @@ describe CassandraRecord::Base do
72
144
  end
73
145
  end
74
146
 
147
+ describe "configuring the database adapter" do
148
+ let(:some_adapter) { double(:some_adapter) }
149
+
150
+ before do
151
+ allow(some_adapter).to receive(:execute) { [] }
152
+
153
+ CassandraRecord::Base.configure do |configuration|
154
+ configuration.database_adapter = some_adapter
155
+ end
156
+ end
157
+
158
+ context ".create" do
159
+ before do
160
+ allow(some_adapter).to receive(:prepare)
161
+ end
162
+
163
+ it "uses the configured database adapter" do
164
+ TestRecord.create(name: 'things')
165
+ expect(some_adapter).to have_received(:prepare)
166
+ expect(some_adapter).to have_received(:execute)
167
+ end
168
+ end
169
+
170
+ context ".where" do
171
+ it "uses the configured database adapter" do
172
+ TestRecord.where(name: 'things')
173
+ expect(some_adapter).to have_received(:execute)
174
+ end
175
+ end
176
+ end
75
177
  end
data/spec/spec_helper.rb CHANGED
@@ -16,8 +16,9 @@ RSpec.configure do |config|
16
16
  db = CassandraRecord::Database::Adapters::Cassandra.instance
17
17
 
18
18
  config.add_setting :db
19
- config.db = db
20
19
  config.add_setting :keyspace
20
+
21
+ config.db = db
21
22
  config.keyspace = 'test_space' # CassandraRecord::Database::Adapters::Cassandra.instance.keyspace
22
23
 
23
24
  create_keyspace = <<-CQL
@@ -3,6 +3,15 @@ require 'spec_helper'
3
3
  describe CassandraRecord::Database::Adapters::Cassandra do
4
4
  subject(:adapter) { CassandraRecord::Database::Adapters::Cassandra.instance }
5
5
 
6
+ before do
7
+ adapter.use(RSpec.configuration.keyspace)
8
+ end
9
+
10
+ after do
11
+ # reset the singleton's @session
12
+ adapter.use(RSpec.configuration.keyspace)
13
+ end
14
+
6
15
  describe "#configuration" do
7
16
  before do
8
17
  adapter.configuration do |config|
@@ -15,4 +24,41 @@ describe CassandraRecord::Database::Adapters::Cassandra do
15
24
  specify { expect(adapter.configuration[:other_thing]).to eq('other_stuff') }
16
25
  end
17
26
 
27
+ describe "connection retries" do
28
+ let(:cluster_connection) { double(:cluster_connection) }
29
+ let(:session) { double(:session) }
30
+ let(:retry_count) { CassandraRecord::Database::Adapters::Cassandra::MAX_RETRIES }
31
+
32
+ before do
33
+ allow(Cassandra).to receive(:cluster) { cluster_connection }
34
+ allow(cluster_connection).to receive(:connect) { session }
35
+ end
36
+
37
+ context "#prepare" do
38
+ before do
39
+ allow(session).to receive(:prepare).and_raise Cassandra::Errors::ClientError
40
+ end
41
+
42
+ it "retries the expected number of times" do
43
+ expect(session).to receive(:prepare).exactly(retry_count).times
44
+ expect {
45
+ adapter.prepare('some bogus commit statement')
46
+ }.to raise_error(Cassandra::Errors::ClientError)
47
+ end
48
+ end
49
+
50
+ context "#execute" do
51
+ before do
52
+ allow(session).to receive(:execute).and_raise Cassandra::Errors::ClientError
53
+ end
54
+
55
+ it "retries the expected number of times" do
56
+ expect(session).to receive(:execute).exactly(retry_count).times
57
+ expect {
58
+ adapter.execute('some bogus commit statement')
59
+ }.to raise_error(Cassandra::Errors::ClientError)
60
+ end
61
+ end
62
+ end
63
+
18
64
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cassandra_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gust
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-17 00:00:00.000000000 Z
11
+ date: 2015-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler