cassandra-mapper 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f2057b9a950ab60c08bff745c6a8cf5fc2492d98
4
+ data.tar.gz: 729c987811f2ab5caa1ebff77ea538977ff2a4e0
5
+ SHA512:
6
+ metadata.gz: 25cc8d2cc84656054045c228ef11e49e93a407acf514ed5bf5ddf8b55a9f970c3d471f2f5f23cf18fc94396ada1b710e2082955122d08bb3e81aaed3d42264c7
7
+ data.tar.gz: c37205509f6be2ba0cf25faf0573f74fa0fe0810a3680cbe57029a1360750f627db801789b294f293ef71060047cd4556391e8a95387ad15a3dc88d39563073a
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ pkg
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-19mode
6
+ - rbx-19mode
7
+ services:
8
+ - cassandra
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: rbx-19mode
12
+ before_install:
13
+ - sudo sh -c "echo 'JVM_OPTS=\"\${JVM_OPTS} -Djava.net.preferIPv4Stack=false\"' >> /usr/local/cassandra/conf/cassandra-env.sh"
14
+ - sudo service cassandra start
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 brainopia
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # Cassandra::Mapper
2
+
3
+ Abstractions to work with cassandra. Currently under development.
4
+
5
+ [![Build
6
+ Status](https://secure.travis-ci.org/brainopia/cassandra-mapper.png)](http://travis-ci.org/brainopia/cassandra-mapper)
7
+ [![Code
8
+ Climate](https://codeclimate.com/github/brainopia/cassandra-mapper.png)](https://codeclimate.com/github/brainopia/cassandra-mapper)
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new :default
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'cassandra-mapper'
3
+ gem.version = '0.1'
4
+ gem.authors = 'brainopia'
5
+ gem.email = 'brainopia@evilmartians.com'
6
+ gem.homepage = 'https://github.com/brainopia/cassandra-mapper'
7
+ gem.summary = 'Cassandra mapper'
8
+ gem.description = <<-DESCRIPTION
9
+ Work with cassandra in datamapper style.
10
+ DESCRIPTION
11
+
12
+ gem.files = `git ls-files`.split($/)
13
+ gem.test_files = gem.files.grep %r{^spec/}
14
+ gem.require_paths = %w(lib)
15
+
16
+ gem.add_dependency 'cassandra', '~> 0.18.0'
17
+ gem.add_dependency 'multi_json', '~> 1.0'
18
+ gem.add_development_dependency 'rspec'
19
+ end
@@ -0,0 +1,41 @@
1
+ require 'bigdecimal'
2
+ require 'time'
3
+ require 'cassandra'
4
+
5
+ Cassandra::THRIFT_DEFAULTS.merge! \
6
+ connect_timeout: 1, timeout: 1
7
+
8
+ class Cassandra::Mapper
9
+ require_relative 'mapper/convert'
10
+ require_relative 'mapper/data/request'
11
+ require_relative 'mapper/data/insert'
12
+ require_relative 'mapper/data/remove'
13
+ require_relative 'mapper/data/response'
14
+
15
+ require_relative 'mapper/extend/schema'
16
+ require_relative 'mapper/extend/migrate'
17
+ require_relative 'mapper/extend/queries'
18
+
19
+ require_relative 'mapper/utility/hash'
20
+ require_relative 'mapper/utility/delegate_keys'
21
+ require_relative 'mapper/utility/config'
22
+ require_relative 'mapper/utility/store_instances'
23
+
24
+ extend Utility::StoreInstances
25
+
26
+ attr_reader :table, :config
27
+
28
+ def initialize(keyspace, table, &block)
29
+ @keyspace = keyspace.to_s
30
+ @table = table.to_s
31
+ @config = Utility::Config.new(&block)
32
+ end
33
+
34
+ def keyspace
35
+ Thread.current["keyspace_#{keyspace_name}"] ||= Cassandra.new keyspace_name
36
+ end
37
+
38
+ def keyspace_name
39
+ "#{@keyspace}_#{env}"
40
+ end
41
+ end
@@ -0,0 +1,95 @@
1
+ module Cassandra::Mapper::Convert
2
+ extend self
3
+
4
+ TEXT_TYPE = 'UTF8Type'
5
+ TYPES = {
6
+ uuid: 'TimeUUIDType',
7
+ integer: 'Int32Type',
8
+ time: 'DateType'
9
+ }
10
+
11
+ def type(symbol)
12
+ TYPES.fetch symbol, TEXT_TYPE
13
+ end
14
+
15
+ def to(type, value)
16
+ send "to_#{type}", value
17
+ end
18
+
19
+ def from(type, value)
20
+ send "from_#{type}", value
21
+ end
22
+
23
+ def round(type, value)
24
+ from type, to(type, value)
25
+ end
26
+
27
+ private
28
+
29
+ def to_(value)
30
+ value.to_s
31
+ end
32
+
33
+ def from_(value)
34
+ value.force_encoding Encoding::UTF_8
35
+ end
36
+
37
+ def to_uuid(value)
38
+ value = Time.parse value if value.is_a? String
39
+ SimpleUUID::UUID.new(value).to_s
40
+ end
41
+
42
+ def from_uuid(value)
43
+ SimpleUUID::UUID.new(value).to_time
44
+ end
45
+
46
+ def to_integer(value)
47
+ [value.to_i].pack('N')
48
+ end
49
+
50
+ def from_integer(value)
51
+ value.unpack('N').first
52
+ end
53
+
54
+ def to_boolean(value)
55
+ value ? 'on' : 'off'
56
+ end
57
+
58
+ def from_boolean(value)
59
+ value == 'on' ? true : false
60
+ end
61
+
62
+ def to_time(value)
63
+ value = Time.parse value if value.is_a? String
64
+ value = value.to_time if value.is_a? Date
65
+ [(value.to_f * 1000).to_i].pack('Q>')
66
+ end
67
+
68
+ def from_time(value)
69
+ Time.at(value.unpack('Q>').first.to_f / 1000)
70
+ end
71
+
72
+ def to_decimal(value)
73
+ value.to_s
74
+ end
75
+
76
+ def from_decimal(value)
77
+ BigDecimal value
78
+ end
79
+
80
+ def to_json(value)
81
+ MultiJson.dump value
82
+ end
83
+
84
+ def from_json(value)
85
+ MultiJson.load value
86
+ end
87
+
88
+ def to_float(value)
89
+ value.to_s
90
+ end
91
+
92
+ def from_float(value)
93
+ value.to_f
94
+ end
95
+ end
@@ -0,0 +1,28 @@
1
+ class Cassandra::Mapper::Data
2
+ class Insert < Request
3
+ def initialize(_config, data)
4
+ @request = data.dup
5
+ super
6
+ end
7
+
8
+ def convert!(data)
9
+ config.before_insert.each {|it| it.call data }
10
+ super
11
+ end
12
+
13
+ def return!
14
+ converted.tap do |data|
15
+ config.after_insert.each {|it| it.call data }
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def converted
22
+ @request.each_with_object({}) do |(field, value), converted|
23
+ next unless value
24
+ converted[field] = Cassandra::Mapper::Convert.round config.types[field], value
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ class Cassandra::Mapper::Data
2
+ class Remove < Insert
3
+ def convert!(data)
4
+ config.before_remove.each {|it| it.call data }
5
+ super
6
+ end
7
+
8
+ def columns
9
+ super.keys
10
+ end
11
+
12
+ def return!
13
+ converted.tap do |data|
14
+ config.after_remove.each {|it| it.call data }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,49 @@
1
+ class Cassandra::Mapper::Data
2
+ class Request
3
+ KEY_SEPARATOR = '##'
4
+
5
+ attr_reader :keys, :subkeys, :data, :config
6
+
7
+ def initialize(config, data)
8
+ @config = config
9
+ @data = convert! data.dup
10
+ @keys = extract! :key
11
+ @subkeys = extract! :subkey
12
+ end
13
+
14
+ def packed_keys
15
+ keys.join(KEY_SEPARATOR).force_encoding('binary')
16
+ end
17
+
18
+ def columns
19
+ fields = data.empty? ? { '' => '' } : data
20
+ fields.each_with_object({}) do |(field, value), columns|
21
+ columns[composite *subkeys, field.to_s] = value
22
+ end
23
+ end
24
+
25
+ def query(offset=nil)
26
+ return { start: offset } if offset
27
+ return if subkeys.all? &:empty?
28
+ { start: composite(*subkeys),
29
+ finish: composite(*subkeys, slice: :after) }
30
+ end
31
+
32
+ private
33
+
34
+ def composite(*args)
35
+ Cassandra::Composite.new *args
36
+ end
37
+
38
+ def extract!(option)
39
+ config.send(option).map {|it| data.delete(it).to_s }
40
+ end
41
+
42
+ def convert!(data)
43
+ data.delete_if {|_, value| value.nil? }
44
+ data.each do |field, value|
45
+ data[field] = Cassandra::Mapper::Convert.to config.types[field], value
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ class Cassandra::Mapper::Data
2
+ class Response
3
+ attr_reader :config, :key_values, :columns
4
+
5
+ def initialize(config, key_values, columns)
6
+ @config = config
7
+ @key_values = key_values
8
+ @columns = columns
9
+ end
10
+
11
+ def keys
12
+ Hash[config.key.zip key_values]
13
+ end
14
+
15
+ def subkeys(values)
16
+ Hash[config.subkey.zip values]
17
+ end
18
+
19
+ def unpack
20
+ return [] if columns.empty?
21
+ records = columns.group_by {|composite, _| composite[0..-2] }
22
+ records.map do |subkey_values, fields|
23
+ record = keys.merge subkeys(subkey_values)
24
+ fields.each do |composite, value|
25
+ field = composite[-1]
26
+ record[field.to_sym] = value unless field.empty?
27
+ end
28
+ convert! record
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def convert!(data)
35
+ data.each do |field, value|
36
+ data[field] = Cassandra::Mapper::Convert.from config.types[field], value
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ class Cassandra::Mapper
2
+ def self.migrate
3
+ cassandra = Cassandra.new('system')
4
+ schema[:keyspaces].each do |name|
5
+ options = schema.fetch(env, {}).fetch(name, {})
6
+ options = Utility::Hash.stringify_keys options
7
+ strategy = options.delete('strategy') || 'SimpleStrategy'
8
+ options['replication_factor'] = options.fetch('replication_factor', 1).to_s
9
+
10
+ cassandra.add_keyspace Cassandra::Keyspace.new \
11
+ name: "#{name}_#{env}",
12
+ strategy_class: strategy,
13
+ strategy_options: options,
14
+ cf_defs: []
15
+ end
16
+ end
17
+
18
+ def migrate
19
+ subkey_types = config.subkey.map do |it|
20
+ Convert.type config.types[it]
21
+ end
22
+
23
+ # field subkey
24
+ subkey_types.push Convert::TEXT_TYPE
25
+
26
+ keyspace.add_column_family Cassandra::ColumnFamily.new \
27
+ keyspace: keyspace.keyspace,
28
+ name: table,
29
+ comparator_type: "CompositeType(#{subkey_types.join ','})"
30
+ end
31
+ end
@@ -0,0 +1,56 @@
1
+ class Cassandra::Mapper
2
+ BATCH_SIZE = 500
3
+
4
+ def insert(hash)
5
+ data = Data::Insert.new config, hash
6
+ keyspace.insert table, data.packed_keys, data.columns
7
+ data.return!
8
+ end
9
+
10
+ def remove(hash)
11
+ data = Data::Remove.new config, hash
12
+ keyspace.remove table, data.packed_keys, data.columns
13
+ data.return!
14
+ end
15
+
16
+ def get(query)
17
+ request = Data::Request.new config, query
18
+ columns = columns_for request
19
+ response = Data::Response.new config, request.keys, columns
20
+ response.unpack
21
+ end
22
+
23
+ def one(keys)
24
+ get(keys).first
25
+ end
26
+
27
+ def each(&block)
28
+ keyspace.each_key table do |packed_keys|
29
+ keys = unpack_keys packed_keys
30
+ get(keys).each &block
31
+ end
32
+ end
33
+
34
+ def all
35
+ to_enum.to_a
36
+ end
37
+
38
+ private
39
+
40
+ def columns_for(request, offset=nil)
41
+ columns = keyspace.get table, request.packed_keys, request.query(offset)
42
+ columns ||= {}
43
+ if columns.size == BATCH_SIZE
44
+ columns.merge! columns_for(request, columns.keys.last)
45
+ end
46
+ columns
47
+ end
48
+
49
+ def unpack_keys(packed_keys)
50
+ keys = packed_keys.split Data::Request::KEY_SEPARATOR
51
+ keys = Hash[config.key.zip(keys)]
52
+ keys.each do |field, value|
53
+ keys[field] = Convert.from config.types[field], value
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ class Cassandra::Mapper
2
+ def self.schema
3
+ @@schema
4
+ end
5
+
6
+ def self.schema=(schema)
7
+ @@schema = Utility::Hash.symbolize_keys schema
8
+ end
9
+
10
+ def self.env
11
+ @@env
12
+ end
13
+
14
+ def self.env=(env)
15
+ @@env = env.to_sym
16
+ end
17
+
18
+ def env
19
+ @@env
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ module Cassandra::Mapper::Utility
2
+ class Config
3
+ extend DelegateKeys
4
+ delegate_keys 'dsl.options', :key, :subkey, :types, :before_insert,
5
+ :after_insert, :after_remove, :before_remove
6
+
7
+ attr_reader :dsl
8
+
9
+ def initialize(&block)
10
+ @dsl = DSL.new &block
11
+ end
12
+
13
+ class DSL
14
+ attr_reader :options
15
+
16
+ def initialize(&block)
17
+ @options = {
18
+ types: {},
19
+ before_insert: [],
20
+ after_insert: [],
21
+ after_remove: [],
22
+ before_remove: []
23
+ }
24
+ instance_eval &block
25
+ end
26
+
27
+ def key(*fields)
28
+ @options[:key] = fields
29
+ end
30
+
31
+ def subkey(*fields)
32
+ @options[:subkey] = fields
33
+ end
34
+
35
+ def type(field, type)
36
+ @options[:types][field] = type
37
+ end
38
+
39
+ def before_insert(&block)
40
+ @options[:before_insert].push block
41
+ end
42
+
43
+ def before_remove(&block)
44
+ @options[:before_remove].push block
45
+ end
46
+
47
+ def after_insert(&block)
48
+ @options[:after_insert].push block
49
+ end
50
+
51
+ def after_remove(&block)
52
+ @options[:after_remove].push block
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,13 @@
1
+ module Cassandra::Mapper::Utility
2
+ module DelegateKeys
3
+ def delegate_keys(target, *keys)
4
+ keys.each do |key|
5
+ class_eval <<-RUBY
6
+ def #{key}
7
+ #{target}[:#{key}]
8
+ end
9
+ RUBY
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module Cassandra::Mapper::Utility
2
+ module Hash
3
+ extend self
4
+
5
+ def symbolize_keys(hash)
6
+ map_hash_key hash, &:to_sym
7
+ end
8
+
9
+ def stringify_keys(hash)
10
+ map_hash_key hash, &:to_s
11
+ end
12
+
13
+ private
14
+
15
+ def map_hash_key(hash)
16
+ hash.keys.each do |key|
17
+ hash[yield key] = hash.delete key
18
+ end
19
+ hash
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Cassandra::Mapper::Utility
2
+ module StoreInstances
3
+ def self.extended(klass)
4
+ klass.instance_variable_set :@instances, []
5
+ end
6
+
7
+ attr_reader :instances
8
+
9
+ def new(*)
10
+ super.tap {|it| @instances << it }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cassandra::Mapper::Convert do
4
+ context 'uuid' do
5
+ let(:time) { Time.now.round }
6
+
7
+ it '#to' do
8
+ subject.to(:uuid, time).should have(16).bytes
9
+ end
10
+
11
+ it '#from' do
12
+ uuid = subject.to(:uuid, time)
13
+ subject.from(:uuid, uuid).should == time
14
+ end
15
+ end
16
+
17
+ context 'integer' do
18
+ let(:number) { 17 }
19
+ let(:cassandra_format) { "\x00\x00\x00\x11" }
20
+
21
+ it '#to' do
22
+ subject.to(:integer, number).should == cassandra_format
23
+ end
24
+
25
+ it '#from' do
26
+ subject.from(:integer, cassandra_format) == number
27
+ end
28
+ end
29
+
30
+ context 'time' do
31
+ context 'empty' do
32
+ it '#to' do
33
+ expect { subject.to(:time, '') }.to raise_error(ArgumentError)
34
+ end
35
+ end
36
+
37
+ let(:time) { Time.at((Time.now.to_f * 1000).to_i / 1000) }
38
+
39
+ it '#from' do
40
+ formatted = subject.to(:time, time)
41
+ subject.from(:time, formatted).should == time
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cassandra::Mapper::Utility::Config do
4
+ subject do
5
+ described_class.new do
6
+ key :key1, :key2
7
+ subkey :subkey1, :subkey2
8
+ type :field, :integer
9
+ end
10
+ end
11
+
12
+ its(:key) { should == [:key1, :key2] }
13
+ its(:subkey) { should == [:subkey1, :subkey2] }
14
+ its(:types) { should == { field: :integer }}
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cassandra::Mapper::Utility::StoreInstances do
4
+ let(:subject) { Class.new.tap {|it| it.extend described_class }}
5
+
6
+ it 'should initialize instances with array' do
7
+ subject.instances.should == []
8
+ end
9
+
10
+ it 'should track new instances' do
11
+ object1 = subject.new
12
+ object2 = subject.new
13
+
14
+ subject.instances.should == [object1, object2]
15
+ end
16
+ end
@@ -0,0 +1,231 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cassandra::Mapper do
4
+ subject do
5
+ described_class.new :mapper, table, &definition
6
+ end
7
+
8
+ before do
9
+ subject.keyspace.drop_column_family table.to_s rescue nil
10
+ subject.migrate
11
+ end
12
+
13
+ let(:table) { :common }
14
+
15
+ context 'one subkey' do
16
+ let :definition do
17
+ proc do
18
+ key :field1
19
+ subkey :field2
20
+ type :field1, :integer
21
+ type :field2, :integer
22
+ type :field3, :integer
23
+ end
24
+ end
25
+
26
+ context '.insert/.get' do
27
+ let(:field1) { 1 }
28
+ let(:field2) { 2 }
29
+ let(:field3) { 3 }
30
+ let(:keys) {{ field1: field1, field2: field2 }}
31
+
32
+ it 'only keys' do
33
+ subject.insert keys
34
+ subject.one(keys).should == keys
35
+ end
36
+
37
+ it 'with data' do
38
+ payload = keys.merge(field3: field3)
39
+ subject.insert(payload).should == payload
40
+ subject.one(keys).should == payload
41
+ end
42
+
43
+ it 'nil data field' do
44
+ payload = keys.merge(field3: nil, field4: nil)
45
+ converted_payload = subject.insert(payload)
46
+ converted_payload.should == keys
47
+ subject.one(keys).should == keys
48
+ end
49
+ end
50
+
51
+ context 'various commands' do
52
+ let(:first) {{ field1: 1, field2: 1, data: 'payload1' }}
53
+ let(:second) {{ field1: 1, field2: 2, data: 'payload2' }}
54
+ let(:data) {[ first, second ]}
55
+
56
+ before { data.each &subject.method(:insert) }
57
+
58
+ it '.each' do
59
+ subject.to_enum.to_a.should == data
60
+ end
61
+
62
+ it '.delete' do
63
+ subject.get(field1: 1).should have(2).items
64
+ subject.remove(first)
65
+ subject.get(field1: 1).should == [second]
66
+ subject.remove(second)
67
+ subject.get(field1: 1).should be_empty
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'callbacks' do
73
+ let :definition do
74
+ proc do
75
+ key :field1
76
+ subkey :field2
77
+ type :field2, :integer
78
+ end
79
+ end
80
+
81
+ it '.before_insert' do
82
+ subject.config.dsl.before_insert do |data|
83
+ data[:field1] = data[:field2]
84
+ end
85
+ subject.insert field2: 2
86
+ subject.one(field1: '2').should == { field1: '2', field2: 2 }
87
+ end
88
+
89
+ it '.after_insert' do
90
+ subject.config.dsl.after_insert do |data|
91
+ data.should == { field1: '1', field2: 2 }
92
+ end
93
+ subject.insert field1: 1, field2: '2'
94
+ end
95
+
96
+ it '.after_remove' do
97
+ subject.config.dsl.after_remove do |data|
98
+ data.should == { field1: '1', field2: 2 }
99
+ end
100
+ subject.insert field1: 1, field2: '2'
101
+ subject.remove field1: 1, field2: '2'
102
+ end
103
+ end
104
+
105
+ context 'conversions' do
106
+ let :definition do
107
+ scope = self
108
+ proc do
109
+ key *scope.key
110
+ subkey *scope.subkey
111
+ type :field, scope.type
112
+ end
113
+ end
114
+
115
+ def self.converts(type, original, expected=original, &block)
116
+ context "converts #{type}" do
117
+ let(:original) { original }
118
+ let(:expected) { expected }
119
+ let(:compare) { block or -> it { it }}
120
+ it_behaves_like :type
121
+ end
122
+ end
123
+
124
+ shared_examples_for :convertable do
125
+ context 'default text type' do
126
+ let(:type) { nil }
127
+ converts 'integer', 2, '2'
128
+ converts 'string', 'string'
129
+ end
130
+
131
+ context 'integer type' do
132
+ let(:type) { :integer }
133
+ converts 'integer', 2
134
+ converts 'big integer', 1_000_000
135
+ converts 'string', '32', 32
136
+ end
137
+
138
+ context 'boolean type' do
139
+ let(:type) { :boolean }
140
+ converts 'true', true
141
+ converts 'false', false
142
+ end
143
+
144
+ context 'time' do
145
+ let(:type) { :time }
146
+ converts 'time', Time.now.round
147
+ converts('date', Date.today) {|time| time.to_date }
148
+ end
149
+
150
+ context 'decimal' do
151
+ let(:type) { :decimal }
152
+ converts 'integer', 20
153
+ converts 'float', 30.442
154
+ converts 'decimal', BigDecimal('42.42')
155
+ end
156
+
157
+ context 'float' do
158
+ let(:type) { :float }
159
+ converts 'integer', 20
160
+ converts 'float', 30.30
161
+ end
162
+ end
163
+
164
+ shared_examples_for :uuid_convertable do
165
+ context 'uuid' do
166
+ let(:type) { :uuid }
167
+
168
+ uuid = SimpleUUID::UUID.new
169
+ converts 'uuid', uuid, uuid.to_time
170
+ converts 'time', Time.now.round
171
+ end
172
+ end
173
+
174
+ shared_examples_for :empty_string_convertable do
175
+ context 'empty string for text type' do
176
+ let(:type) { nil }
177
+ converts '', ''
178
+ end
179
+ end
180
+
181
+ shared_examples_for :type do
182
+ before { subject.insert data }
183
+
184
+ it 'should be correctly restored' do
185
+ restored = subject.one(query)[:field]
186
+ compare.(restored).should == expected
187
+ end
188
+ end
189
+
190
+ context 'key' do
191
+ let(:key) { :field }
192
+ let(:subkey) {}
193
+ let(:query) {{ field: original }}
194
+ let(:data) {{ field: original, data: :dummy }}
195
+
196
+ it_behaves_like :convertable
197
+ end
198
+
199
+ context 'subkeys' do
200
+ let(:key) { :key }
201
+ let(:subkey) { :field }
202
+ let(:query) {{ key: 42 }}
203
+ let(:data) {{ key: 42, field: original }}
204
+
205
+ it_behaves_like :convertable
206
+ it_behaves_like :uuid_convertable
207
+ it_behaves_like :empty_string_convertable
208
+ end
209
+
210
+ context 'data' do
211
+ let(:key) { :key }
212
+ let(:subkey) {}
213
+ let(:query) {{ key: 42 }}
214
+ let(:data) {{ key: 42, field: original }}
215
+
216
+ it_behaves_like :convertable
217
+ it_behaves_like :uuid_convertable
218
+ it_behaves_like :empty_string_convertable
219
+ end
220
+
221
+ context 'complex key' do
222
+ let(:key) {[ :key, :field ]}
223
+ let(:subkey) {}
224
+ let(:query) {{ key: 42, field: original }}
225
+ let(:data) {{ key: 42, field: original, data: :dummy }}
226
+
227
+ it_behaves_like :convertable
228
+ it_behaves_like :empty_string_convertable
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,11 @@
1
+ require 'cassandra/mapper'
2
+ require 'yaml'
3
+
4
+ Cassandra::Mapper.schema = { keyspaces: [ :mapper ]}
5
+ Cassandra::Mapper.env = :test
6
+
7
+ begin
8
+ Cassandra::Mapper.migrate
9
+ rescue CassandraThrift::InvalidRequestException
10
+ puts 'Using existing keyspace'
11
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cassandra-mapper
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - brainopia
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cassandra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.18.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.18.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: |2
56
+ Work with cassandra in datamapper style.
57
+ email: brainopia@evilmartians.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - .travis.yml
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - cassandra-mapper.gemspec
69
+ - lib/cassandra/mapper.rb
70
+ - lib/cassandra/mapper/convert.rb
71
+ - lib/cassandra/mapper/data/insert.rb
72
+ - lib/cassandra/mapper/data/remove.rb
73
+ - lib/cassandra/mapper/data/request.rb
74
+ - lib/cassandra/mapper/data/response.rb
75
+ - lib/cassandra/mapper/extend/migrate.rb
76
+ - lib/cassandra/mapper/extend/queries.rb
77
+ - lib/cassandra/mapper/extend/schema.rb
78
+ - lib/cassandra/mapper/utility/config.rb
79
+ - lib/cassandra/mapper/utility/delegate_keys.rb
80
+ - lib/cassandra/mapper/utility/hash.rb
81
+ - lib/cassandra/mapper/utility/store_instances.rb
82
+ - spec/cassandra/mapper/convert_spec.rb
83
+ - spec/cassandra/mapper/utility/config_spec.rb
84
+ - spec/cassandra/mapper/utility/store_instances_spec.rb
85
+ - spec/cassandra/mapper_spec.rb
86
+ - spec/spec_helper.rb
87
+ homepage: https://github.com/brainopia/cassandra-mapper
88
+ licenses: []
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.0.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Cassandra mapper
110
+ test_files:
111
+ - spec/cassandra/mapper/convert_spec.rb
112
+ - spec/cassandra/mapper/utility/config_spec.rb
113
+ - spec/cassandra/mapper/utility/store_instances_spec.rb
114
+ - spec/cassandra/mapper_spec.rb
115
+ - spec/spec_helper.rb
116
+ has_rdoc: