active_column 0.0.2 → 0.1
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.
- data/.gitignore +1 -0
- data/.yardopts +1 -0
- data/Gemfile.lock +120 -17
- data/README.md +27 -143
- data/active_column.gemspec +12 -6
- data/docs/Create.md +101 -0
- data/docs/Migrate.md +100 -0
- data/docs/Query.md +43 -0
- data/lib/active_column.rb +22 -3
- data/lib/active_column/base.rb +32 -46
- data/lib/active_column/errors.rb +6 -0
- data/lib/active_column/generators/migration_generator.rb +31 -0
- data/lib/active_column/generators/templates/migration.rb.erb +11 -0
- data/lib/active_column/key_config.rb +16 -0
- data/lib/active_column/migration.rb +269 -0
- data/lib/active_column/tasks/column_family.rb +64 -0
- data/lib/active_column/tasks/keyspace.rb +59 -0
- data/lib/active_column/tasks/ks.rb +76 -0
- data/lib/active_column/version.rb +1 -1
- data/spec/active_column/base_crud_spec.rb +1 -1
- data/spec/active_column/base_finders_spec.rb +6 -6
- data/spec/active_column/migrator_spec.rb +150 -0
- data/spec/active_column/tasks/column_family_spec.rb +34 -0
- data/spec/active_column/tasks/keyspace_spec.rb +38 -0
- data/spec/spec_helper.rb +24 -4
- data/spec/support/aggregating_tweet.rb +3 -1
- data/spec/support/migrate/migrator_spec/1_migration1.rb +11 -0
- data/spec/support/migrate/migrator_spec/2_migration2.rb +11 -0
- data/spec/support/migrate/migrator_spec/3_migration3.rb +11 -0
- data/spec/support/migrate/migrator_spec/4_migration4.rb +11 -0
- data/spec/support/tweet.rb +4 -2
- data/spec/support/tweet_dm.rb +6 -4
- metadata +103 -11
@@ -0,0 +1,64 @@
|
|
1
|
+
module ActiveColumn
|
2
|
+
|
3
|
+
module Tasks
|
4
|
+
|
5
|
+
class ColumnFamily
|
6
|
+
|
7
|
+
COMPARATOR_TYPES = { :time => 'TimeUUIDType',
|
8
|
+
:timestamp => 'TimeUUIDType',
|
9
|
+
:long => 'LongType',
|
10
|
+
:string => 'BytesType' }
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@cassandra = ActiveColumn.connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def exists?(name)
|
17
|
+
@cassandra.schema.cf_defs.find { |cf_def| cf_def.name == name.to_s }
|
18
|
+
end
|
19
|
+
|
20
|
+
def create(name, options = {})
|
21
|
+
opts = { :name => name.to_s,
|
22
|
+
:keyspace => @cassandra.keyspace,
|
23
|
+
:comparator_type => 'TimeUUIDType' }.merge(options)
|
24
|
+
|
25
|
+
cf = Cassandra::ColumnFamily.new.with_fields(opts)
|
26
|
+
@cassandra.add_column_family(cf)
|
27
|
+
end
|
28
|
+
|
29
|
+
def drop(name)
|
30
|
+
@cassandra.drop_column_family(name.to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear(name)
|
34
|
+
@cassandra.truncate!(name.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def post_process_options(options)
|
40
|
+
type = options[:comparator_type]
|
41
|
+
if type && COMPARATOR_TYPES.has_key?(type)
|
42
|
+
options[:comparator_type] = COMPARATOR_TYPES[type]
|
43
|
+
end
|
44
|
+
options
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
class Cassandra
|
54
|
+
class ColumnFamily
|
55
|
+
def with_fields(options)
|
56
|
+
struct_fields.collect { |f| f[1][:name] }.each do |f|
|
57
|
+
send("#{f}=", options[f.to_sym])
|
58
|
+
end
|
59
|
+
self
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ActiveColumn
|
2
|
+
|
3
|
+
module Tasks
|
4
|
+
|
5
|
+
class Keyspace
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
c = ActiveColumn.connection
|
9
|
+
@cassandra = Cassandra.new('system', c.servers, c.thrift_client_options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def exists?(name)
|
13
|
+
@cassandra.keyspaces.include? name.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def create(name, options = {})
|
17
|
+
opts = { :name => name.to_s,
|
18
|
+
:strategy_class => 'org.apache.cassandra.locator.LocalStrategy',
|
19
|
+
:replication_factor => 1,
|
20
|
+
:cf_defs => [] }.merge(options)
|
21
|
+
|
22
|
+
ks = Cassandra::Keyspace.new.with_fields(opts)
|
23
|
+
@cassandra.add_keyspace ks
|
24
|
+
end
|
25
|
+
|
26
|
+
def drop(name)
|
27
|
+
@cassandra.drop_keyspace name.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def set(name)
|
31
|
+
@cassandra.keyspace = name.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def get
|
35
|
+
@cassandra.keyspace
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear
|
39
|
+
return puts 'Cannot clear system keyspace' if @cassandra.keyspace == 'system'
|
40
|
+
|
41
|
+
@cassandra.clear_keyspace!
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
class Cassandra
|
51
|
+
class Keyspace
|
52
|
+
def with_fields(options)
|
53
|
+
struct_fields.collect { |f| f[1][:name] }.each do |f|
|
54
|
+
send("#{f}=", options[f.to_sym])
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
namespace :ks do
|
4
|
+
|
5
|
+
desc 'Create the keyspace in config/cassandra.yml for the current environment'
|
6
|
+
task :create => :environment do
|
7
|
+
config = load_config[Rails.env || 'development']
|
8
|
+
ks = config['keyspace']
|
9
|
+
ActiveColumn::Tasks::Keyspace.new.create ks, config
|
10
|
+
puts "Created keyspace: #{ks}"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Create keyspaces in config/cassandra.yml for all environments'
|
14
|
+
task 'create:all' => :environment do
|
15
|
+
config = load_config
|
16
|
+
kss = []
|
17
|
+
config.keys.each do |env|
|
18
|
+
kss << config[env]['keyspace']
|
19
|
+
ActiveColumn::Tasks::Keyspace.new.create kss.last, config
|
20
|
+
end
|
21
|
+
puts "Created keyspaces: #{kss.join(', ')}"
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Drop keyspace in config/cassandra.yml for the current environment'
|
25
|
+
task :drop => :environment do
|
26
|
+
config = load_config[Rails.env || 'development']
|
27
|
+
ks = config['keyspace']
|
28
|
+
ActiveColumn::Tasks::Keyspace.new.drop ks
|
29
|
+
puts "Dropped keyspace: #{ks}"
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Drop keyspaces in config/cassandra.yml for all environments'
|
33
|
+
task 'drop:all' => :environment do
|
34
|
+
config = load_config
|
35
|
+
kss = []
|
36
|
+
config.keys.each do |env|
|
37
|
+
kss << config[env]['keyspace']
|
38
|
+
ActiveColumn::Tasks::Keyspace.new.drop kss.last
|
39
|
+
end
|
40
|
+
puts "Dropped keyspaces: #{kss.join(', ')}"
|
41
|
+
end
|
42
|
+
|
43
|
+
desc 'Migrate the keyspace (options: VERSION=x)'
|
44
|
+
task :migrate => :environment do
|
45
|
+
set_keyspace
|
46
|
+
version = ( ENV['VERSION'] ? ENV['VERSION'].to_i : nil )
|
47
|
+
ActiveColumn::Migrator.migrate ActiveColumn::Migrator.migrations_path, version
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n)'
|
51
|
+
task :rollback => :environment do
|
52
|
+
set_keyspace
|
53
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
54
|
+
ActiveColumn::Migrator.rollback ActiveColumn::Migrator.migrations_path, step
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'Pushes the schema to the next version (specify steps w/ STEP=n)'
|
58
|
+
task :forward => :environment do
|
59
|
+
set_keyspace
|
60
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
61
|
+
ActiveColumn::Migrator.forward ActiveColumn::Migrator.migrations_path, step
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def load_config
|
67
|
+
YAML.load_file(Rails.root.join("config", "cassandra.yml"))
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_keyspace
|
71
|
+
config = load_config[Rails.env || 'development']
|
72
|
+
ActiveColumn::Tasks::Keyspace.new.set config['keyspace']
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
@@ -20,7 +20,7 @@ describe ActiveColumn::Base do
|
|
20
20
|
it 'find all of the models' do
|
21
21
|
@found.size.should == 1
|
22
22
|
@found['user1'].size.should == 3
|
23
|
-
@found['user1'].collect { |t| t.
|
23
|
+
@found['user1'].collect { |t| t.message }.should == [ 'Now im full', 'Now im hungry', 'Going running' ]
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -31,8 +31,8 @@ describe ActiveColumn::Base do
|
|
31
31
|
|
32
32
|
it 'finds all of the models' do
|
33
33
|
@found.size.should == 2
|
34
|
-
@found['user1'].collect { |t| t.
|
35
|
-
@found['user2'].collect { |t| t.
|
34
|
+
@found['user1'].collect { |t| t.message }.should == [ 'Now im full' ]
|
35
|
+
@found['user2'].collect { |t| t.message }.should == [ 'Watching TV' ]
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -50,9 +50,9 @@ describe ActiveColumn::Base do
|
|
50
50
|
|
51
51
|
it 'finds all of the models' do
|
52
52
|
@found.size.should == 6
|
53
|
-
@found['user1:friend1'].collect { |t| t.
|
54
|
-
@found['user1:friend2'].collect { |t| t.
|
55
|
-
@found['user1:all'].collect { |t| t.
|
53
|
+
@found['user1:friend1'].collect { |t| t.message }.should == [ 'Need to do laundry' ]
|
54
|
+
@found['user1:friend2'].collect { |t| t.message }.should == [ 'My leg itches' ]
|
55
|
+
@found['user1:all'].collect { |t| t.message }.should == [ 'My leg itches' ]
|
56
56
|
@found['user2:friend1'].should == []
|
57
57
|
@found['user2:friend2'].should == []
|
58
58
|
@found['user2:all'].should == []
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
$migrator_spec_data = {}
|
4
|
+
|
5
|
+
def get_migrations
|
6
|
+
$cassandra.get(:schema_migrations, 'all').map {|name, _value| name.to_i}
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_data
|
10
|
+
$migrator_spec_data.keys.sort
|
11
|
+
end
|
12
|
+
|
13
|
+
migrations_path = File.expand_path("../../support/migrate/migrator_spec", __FILE__)
|
14
|
+
|
15
|
+
describe ActiveColumn::Migrator do
|
16
|
+
|
17
|
+
after do
|
18
|
+
$cassandra.truncate!("schema_migrations")
|
19
|
+
$migrator_spec_data.clear
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.migrate' do
|
23
|
+
context 'given no previous migrations and some pending migrations and no target version' do
|
24
|
+
before do
|
25
|
+
ActiveColumn::Migrator.migrate(migrations_path)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'runs the migrations' do
|
29
|
+
assert { get_migrations == [1, 2, 3, 4] }
|
30
|
+
assert { get_data == [1, 2, 3, 4] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'given no previous migrations and some pending migrations and a target version' do
|
35
|
+
before do
|
36
|
+
ActiveColumn::Migrator.migrate(migrations_path, 2)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'runs the migrations' do
|
40
|
+
assert { get_migrations == [1, 2] }
|
41
|
+
assert { get_data == [1, 2] }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'given no previous migrations and no pending migrations' do
|
46
|
+
before do
|
47
|
+
ActiveColumn::Migrator.migrate(File.expand_path("./fake", migrations_path))
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'runs no migrations' do
|
51
|
+
assert { get_migrations == [] }
|
52
|
+
assert { get_data == [] }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'given some previous migrations and no target version' do
|
57
|
+
before do
|
58
|
+
ActiveColumn::Migrator.migrate(migrations_path, 2)
|
59
|
+
ActiveColumn::Migrator.migrate(migrations_path)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'runs the migrations' do
|
63
|
+
assert { get_migrations == [1, 2, 3, 4]}
|
64
|
+
assert { get_data == [1, 2, 3, 4]}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'given some previous migrations and a target version up' do
|
69
|
+
|
70
|
+
before do
|
71
|
+
ActiveColumn::Migrator.migrate(migrations_path, 2)
|
72
|
+
ActiveColumn::Migrator.migrate(migrations_path, 3)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'runs the migrations' do
|
76
|
+
assert { get_migrations == [1, 2, 3] }
|
77
|
+
assert { get_data == [1, 2, 3] }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'given some previous migrations and a target version down' do
|
82
|
+
before do
|
83
|
+
ActiveColumn::Migrator.migrate(migrations_path, 2)
|
84
|
+
ActiveColumn::Migrator.migrate(migrations_path, 1)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'rolls back the migrations' do
|
88
|
+
assert { get_migrations == [1] }
|
89
|
+
assert { get_data == [1] }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '.rollback' do
|
95
|
+
before do
|
96
|
+
ActiveColumn::Migrator.migrate migrations_path, 3
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'given no steps' do
|
100
|
+
before do
|
101
|
+
ActiveColumn::Migrator.rollback migrations_path
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'rolls back one step' do
|
105
|
+
assert { get_migrations == [1, 2] }
|
106
|
+
assert { get_data == [1, 2] }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'given steps = 2' do
|
111
|
+
before do
|
112
|
+
ActiveColumn::Migrator.rollback migrations_path, 2
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'rolls back two steps' do
|
116
|
+
assert { get_migrations == [1] }
|
117
|
+
assert { get_data == [1] }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '.forward' do
|
123
|
+
before do
|
124
|
+
ActiveColumn::Migrator.migrate migrations_path, 1
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'given no steps' do
|
128
|
+
before do
|
129
|
+
ActiveColumn::Migrator.forward migrations_path
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'migrates one step' do
|
133
|
+
assert { get_migrations == [1, 2] }
|
134
|
+
assert { get_data == [1, 2] }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'given steps = 2' do
|
139
|
+
before do
|
140
|
+
ActiveColumn::Migrator.forward migrations_path, 2
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'migrates two steps' do
|
144
|
+
assert { get_migrations == [1, 2, 3] }
|
145
|
+
assert { get_data == [1, 2, 3] }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def translated_comparator(given)
|
4
|
+
cf = ActiveColumn::Tasks::ColumnFamily.new
|
5
|
+
cf.send(:post_process_options, { :comparator_type => given })[:comparator_type]
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ActiveColumn::Tasks::ColumnFamily do
|
9
|
+
|
10
|
+
describe '.post_process_options' do
|
11
|
+
context 'given a time-based comparator_type' do
|
12
|
+
it 'sets TimeUUIDType' do
|
13
|
+
assert { translated_comparator(:time) == 'TimeUUIDType' }
|
14
|
+
assert { translated_comparator(:timestamp) == 'TimeUUIDType' }
|
15
|
+
assert { translated_comparator('TimeUUIDType') == 'TimeUUIDType' }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'given a long-based comparator_type' do
|
20
|
+
it 'sets LongType' do
|
21
|
+
assert { translated_comparator(:long) == 'LongType' }
|
22
|
+
assert { translated_comparator('LongType') == 'LongType' }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'given a string-based comparator_type' do
|
27
|
+
it 'sets BytesType' do
|
28
|
+
assert { translated_comparator(:string) == 'BytesType' }
|
29
|
+
assert { translated_comparator('BytesType') == 'BytesType' }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveColumn::Tasks::Keyspace do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@ks = ActiveColumn::Tasks::Keyspace.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".create" do
|
10
|
+
context "given a keyspace" do
|
11
|
+
before do
|
12
|
+
@ks.drop :ks_create_test if @ks.exists?(:ks_create_test)
|
13
|
+
@ks.create :ks_create_test
|
14
|
+
end
|
15
|
+
|
16
|
+
it "creates the keyspace" do
|
17
|
+
@ks.exists?(:ks_create_test).should be
|
18
|
+
end
|
19
|
+
|
20
|
+
after do
|
21
|
+
@ks.drop :ks_create_test
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.drop' do
|
27
|
+
context 'given a keyspace' do
|
28
|
+
before do
|
29
|
+
@ks.create :ks_drop_test unless @ks.exists?(:ks_drop_test)
|
30
|
+
@ks.drop :ks_drop_test
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'drops the keyspace' do
|
34
|
+
@ks.exists?(:ks_drop_test).should_not be
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|