cassandra_record 0.0.5 → 0.1.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 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