cassandra_migrations 0.0.1.pre4 → 0.0.1.pre5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'fileutils'
5
+ require 'colorize'
6
+
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: #{File.basename($0)} [path]"
9
+
10
+ opts.on("-h", "--help", "Displays this help info") do
11
+ puts opts
12
+ exit 0
13
+ end
14
+
15
+ begin
16
+ opts.parse!(ARGV)
17
+ rescue OptionParser::ParseError => e
18
+ warn e.message
19
+ puts opts
20
+ exit 1
21
+ end
22
+ end
23
+
24
+ if ARGV.empty?
25
+ abort "Please specify the directory of a rails appilcation, e.g. `#{File.basename($0)} .'"
26
+ elsif !File.directory?(ARGV.first)
27
+ abort "`#{ARGV.first}' is not a directory."
28
+ elsif ARGV.length > 1
29
+ abort "Too many arguments; please specify only the directory of the rails application."
30
+ end
31
+
32
+ rails_root = ARGV.first
33
+
34
+ # create cassandra.yaml
35
+ if File.exists?(File.expand_path('config/cassandra.yml', rails_root))
36
+ puts "[skip] 'config/cassandra.yml' already exists".yellow
37
+ else
38
+ puts "[new] creating 'config/cassandra.yml' (please update with your own configurations!)".green
39
+ FileUtils.cp(
40
+ File.expand_path('../template/cassandra.yml', File.dirname(__FILE__)),
41
+ File.expand_path('config/cassandra.yml', rails_root)
42
+ )
43
+ end
44
+
45
+ # create db/cassandra_migrations
46
+ if File.exists?(File.expand_path('db/cassandra_migrate', rails_root))
47
+ puts "[skip] 'db/cassandra_migrate' already exists".yellow
48
+ else
49
+ puts "[new] creating 'db/cassandra_migrate' directory".green
50
+ FileUtils.mkdir(File.expand_path('db/cassandra_migrate', rails_root))
51
+ end
52
+
53
+ puts '[done] prepared for cassandra!'.green
54
+ puts ''
55
+ puts 'Your steps from here are:'.green
56
+ puts ' 1. configure '.green + 'config/cassandra.yml'.red
57
+ puts ' 2. run '.green + 'rake cassandra:setup'.red + ' and try starting your application'.green
58
+ puts ' 3. create your first migration with '.green + 'rails g cassandra_migration'.red
59
+ puts ' 4. apply your migration with '.green + 'rake cassandra:migrate'.red
60
+ puts ' 5. run '.green + 'rake cassandra:test:prepare'.red + 'and start testing'.green
61
+ puts ' 6. have lots of fun!'.green.blink
62
+
@@ -2,5 +2,6 @@ require 'cassandra_migrations/config'
2
2
  require 'cassandra_migrations/errors'
3
3
  require 'cassandra_migrations/cassandra'
4
4
  require 'cassandra_migrations/migrator'
5
+ require 'cassandra_migrations/migration'
5
6
 
6
7
  require 'cassandra_migrations/railtie' if defined?(Rails)
@@ -5,7 +5,6 @@ require 'cql'
5
5
  require 'cassandra_migrations/cassandra/queries'
6
6
  require 'cassandra_migrations/cassandra/keyspace_operations'
7
7
 
8
-
9
8
  module CassandraMigrations
10
9
  module Cassandra
11
10
  extend Queries
@@ -10,7 +10,14 @@ module CassandraMigrations
10
10
 
11
11
  hash.each do |k,v|
12
12
  columns << k.to_s
13
- values << (v.is_a?(String) ? "'#{v.to_s}'" : v.to_s)
13
+
14
+ if v.respond_to?(:strftime)
15
+ values << "'#{v.strftime('%Y-%m-%d %H:%M:%S%z')}'"
16
+ elsif v.is_a?(String)
17
+ values << "'#{v}'"
18
+ else
19
+ values << v.to_s
20
+ end
14
21
  end
15
22
 
16
23
  execute("INSERT INTO #{table} (#{columns.join(', ')}) VALUES (#{values.join(', ')})")
@@ -38,5 +38,11 @@ module CassandraMigrations
38
38
  end
39
39
  end
40
40
 
41
+ class MigrationDefinitionError < CassandraError
42
+ def initialize(msg)
43
+ super(msg)
44
+ end
45
+ end
46
+
41
47
  end
42
48
  end
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+
3
+ require 'cassandra_migrations/migration/table_operations'
4
+ require 'cassandra_migrations/migration/column_operations'
5
+
6
+ module CassandraMigrations
7
+
8
+ # Base class for all cassandra migration
9
+ class Migration
10
+
11
+ include TableOperations
12
+ include ColumnOperations
13
+
14
+ # Makes +execute+ method directly available to migrations
15
+ delegate :execute, :to => Cassandra
16
+
17
+ # Makes +up+ work if the method in the migration is defined with self.up
18
+ def up
19
+ return unless self.class.respond_to?(:up)
20
+ self.class.instance_to_delegate = self
21
+ self.class.up
22
+ end
23
+
24
+ # Makes +down+ work if the method in the migration is defined with self.down
25
+ def down
26
+ return unless self.class.respond_to?(:down)
27
+ self.class.instance_to_delegate = self
28
+ self.class.down
29
+ end
30
+
31
+ # Class variable that holds an instance of Migration when the methods +up+ or
32
+ # +down+ are called on the class. The class then delegates missing method
33
+ # calls to this instance.
34
+ cattr_accessor :instance_to_delegate, :instance_accessor => false
35
+
36
+ # Delegate missing method calls to an instance. That's what enables the
37
+ # writing of migrations using both +def up+ and +def self.up+ sintax.
38
+ def self.method_missing(name, *args, &block)
39
+ if instance_to_delegate
40
+ instance_to_delegate.send(name, *args, &block)
41
+ else
42
+ super
43
+ end
44
+ end
45
+
46
+ # Execute this migration in the named direction.
47
+ #
48
+ # The advantage of using this instead of directly calling up or down is that
49
+ # this method gives informative output and benchmarks the time taken.
50
+ def migrate(direction)
51
+ return unless respond_to?(direction)
52
+
53
+ case direction
54
+ when :up then announce_migration "migrating"
55
+ when :down then announce_migration "reverting"
56
+ end
57
+
58
+ time = Benchmark.measure { send(direction) }
59
+
60
+ case direction
61
+ when :up then announce_migration "migrated (%.4fs)" % time.real; puts
62
+ when :down then announce_migration "reverted (%.4fs)" % time.real; puts
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ # Generates output labeled with name of migration and a line that goes up
69
+ # to 75 characters long in the terminal
70
+ def announce_migration(message)
71
+ text = "#{name}: #{message}"
72
+ length = [0, 75 - text.length].max
73
+ puts "== %s %s" % [text, "=" * length]
74
+ end
75
+
76
+ def announce_operation(message)
77
+ puts " " + message
78
+ end
79
+
80
+ def announce_suboperation(message)
81
+ puts " -> " + message
82
+ end
83
+
84
+ # Gets the name of the migration
85
+ def name
86
+ self.class.name
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ require 'cassandra_migrations/migration/table_definition'
4
+
5
+ module CassandraMigrations
6
+ class Migration
7
+
8
+ # Module grouping methods used in migrations to make table operations like:
9
+ # - adding/removing columns
10
+ # - changing column types
11
+ # - renaming columns
12
+ module ColumnOperations
13
+
14
+ # Adds a column to a table.
15
+ #
16
+ # options: same options you would pass to create a table with that column
17
+ # (i.e. :limit might be applicable)
18
+
19
+ def add_column(table_name, column_name, type, options = {})
20
+ table_definition = TableDefinition.new
21
+
22
+ if !table_definition.respond_to?(type)
23
+ raise Errors::MigrationDefinitionError("Type '#{type}' is not valid for cassandra migration.")
24
+ end
25
+
26
+ table_definition.send(type, column_name, options)
27
+
28
+ announce_operation "add_column(#{column_name}, #{type})"
29
+
30
+ cql = "ALTER TABLE #{table_name} ADD "
31
+ cql << table_definition.to_add_column_cql
32
+ announce_suboperation cql
33
+
34
+ execute cql
35
+ end
36
+
37
+ # Removes a column from the table
38
+ def remove_column(table_name, column_name)
39
+ announce_operation "drop_table(#{table_name})"
40
+
41
+ cql = "ALTER TABLE #{table_name} DROP #{column_name}"
42
+ announce_suboperation cql
43
+
44
+ execute cql
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,111 @@
1
+ # encoding: utf-8
2
+
3
+ module CassandraMigrations
4
+ class Migration
5
+
6
+ # Used to define a table in a migration of table creation or to
7
+ # add columns to an existing table.
8
+ #
9
+ # An instance of this class is passed to the block of the method
10
+ # +create_table+, available on every migration.
11
+ #
12
+ # This class is also internally used in the method +add_column+.
13
+
14
+ class TableDefinition
15
+
16
+ def initialize()
17
+ @columns_name_type_hash = {}
18
+ @primary_keys = []
19
+ end
20
+
21
+ def to_create_cql
22
+ cql = []
23
+
24
+ if !@columns_name_type_hash.empty?
25
+ @columns_name_type_hash.each do |column_name, type|
26
+ cql << "#{column_name} #{type}"
27
+ end
28
+ else
29
+ raise Errors::MigrationDefinitionError('No columns defined for table.')
30
+ end
31
+
32
+ if !@primary_keys.empty?
33
+ cql << "PRIMARY KEY(#{@primary_keys.join(', ')})"
34
+ else
35
+ raise Errors::MigrationDefinitionError('No primary key defined.')
36
+ end
37
+
38
+ cql.join(', ')
39
+ end
40
+
41
+ def to_add_column_cql
42
+ cql = ""
43
+
44
+ if @columns_name_type_hash.size == 1
45
+ cql = "#{@columns_name_type_hash.keys.first} #{@columns_name_type_hash.values.first}"
46
+ elsif @columns_name_type_hash.empty?
47
+ raise Errors::MigrationDefinitionError('No column to add.')
48
+ else
49
+ raise Errors::MigrationDefinitionError('Only one column ca be added at once.')
50
+ end
51
+
52
+ cql
53
+ end
54
+
55
+ def boolean(column_name, options={})
56
+ @columns_name_type_hash[column_name.to_sym] = :boolean
57
+ define_primary_keys(column_name) if options[:primary_key]
58
+ end
59
+
60
+ def integer(column_name, options={})
61
+ if options[:limit].nil? || options[:limit] == 4
62
+ @columns_name_type_hash[column_name.to_sym] = :int
63
+ elsif options[:limit] == 8
64
+ @columns_name_type_hash[column_name.to_sym] = :bigint
65
+ else
66
+ raise Errors::MigrationDefinitionError(':limit option should be 4 or 8 for integers.')
67
+ end
68
+ define_primary_keys(column_name) if options[:primary_key]
69
+ end
70
+
71
+ def float(column_name, options={})
72
+ if options[:limit].nil? || options[:limit] == 4
73
+ @columns_name_type_hash[column_name.to_sym] = :float
74
+ elsif options[:limit] == 8
75
+ @columns_name_type_hash[column_name.to_sym] = :double
76
+ else
77
+ raise Errors::MigrationDefinitionError(':limit option should be 4 or 8 for floats.')
78
+ end
79
+ define_primary_keys(column_name) if options[:primary_key]
80
+ end
81
+
82
+ def string(column_name, options={})
83
+ @columns_name_type_hash[column_name.to_sym] = :varchar
84
+ define_primary_keys(column_name) if options[:primary_key]
85
+ end
86
+
87
+ def text(column_name, options={})
88
+ @columns_name_type_hash[column_name.to_sym] = :text
89
+ define_primary_keys(column_name) if options[:primary_key]
90
+ end
91
+
92
+ def datetime(column_name, options={})
93
+ @columns_name_type_hash[column_name.to_sym] = :timestamp
94
+ define_primary_keys(column_name) if options[:primary_key]
95
+ end
96
+
97
+ def timestamp(column_name, options={})
98
+ @columns_name_type_hash[column_name.to_sym] = :timestamp
99
+ define_primary_keys(column_name) if options[:primary_key]
100
+ end
101
+
102
+ def define_primary_keys(*keys)
103
+ if !@primary_keys.empty?
104
+ raise Errors::MigrationDefinitionError('Primary key defined twice for the same table.')
105
+ end
106
+
107
+ @primary_keys = keys.flatten
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ require 'cassandra_migrations/migration/table_definition'
4
+
5
+ module CassandraMigrations
6
+ class Migration
7
+
8
+ # Module grouping methods used in migrations to make table operations like:
9
+ # - creating tables
10
+ # - dropping tables
11
+ module TableOperations
12
+
13
+ # Creates a new table in the keyspace
14
+ #
15
+ # options:
16
+ # - :primary_keys: single value or array (for compound primary keys). If
17
+ # not defined, some column must be chosen as primary key in the table definition.
18
+
19
+ def create_table(table_name, options = {})
20
+ table_definition = TableDefinition.new
21
+ table_definition.define_primary_keys(options[:primary_keys]) if options[:primary_keys]
22
+
23
+ yield table_definition if block_given?
24
+
25
+ announce_operation "create_table(#{table_name})"
26
+
27
+ create_cql = "CREATE TABLE #{table_name} ("
28
+ create_cql << table_definition.to_create_cql
29
+ create_cql << ")"
30
+
31
+ announce_suboperation create_cql
32
+
33
+ execute create_cql
34
+ end
35
+
36
+ # Drops a table
37
+ def drop_table(table_name)
38
+ announce_operation "drop_table(#{table_name})"
39
+ drop_cql = "DROP TABLE #{table_name}"
40
+ announce_suboperation drop_cql
41
+
42
+ execute drop_cql
43
+ end
44
+ end
45
+ end
46
+ end
@@ -31,7 +31,7 @@ module CassandraMigrations
31
31
  if !executed_migrations.empty?
32
32
  count.times do |i|
33
33
  if executed_migrations[i]
34
- down(executed_migrations[i], executed_migrations[i])
34
+ down(executed_migrations[i], executed_migrations[i+1])
35
35
  down_count += 1
36
36
  end
37
37
  end
@@ -56,7 +56,7 @@ private
56
56
  # load migration
57
57
  require migration_name
58
58
  # run migration
59
- get_class_from_migration_name(migration_name).up
59
+ get_class_from_migration_name(migration_name).new.migrate(:up)
60
60
 
61
61
  # update version
62
62
  Cassandra.write!(METADATA_TABLE, {:data_name => 'version', :data_value => get_version_from_migration_name(migration_name).to_s})
@@ -66,8 +66,8 @@ private
66
66
  # load migration
67
67
  require migration_name
68
68
  # run migration
69
- get_class_from_migration_name(migration_name).down
70
-
69
+ get_class_from_migration_name(migration_name).new.migrate(:down)
70
+
71
71
  # downgrade version
72
72
  if previous_migration_name
73
73
  Cassandra.write!(METADATA_TABLE, {:data_name => 'version', :data_value => get_version_from_migration_name(previous_migration_name).to_s})
@@ -13,4 +13,10 @@ class CassandraMigrations::Railtie < ::Rails::Railtie
13
13
  end
14
14
  end
15
15
 
16
+ generators do
17
+ Dir[File.expand_path("railtie/**/*_generator.rb", File.dirname(__FILE__))].each do |file|
18
+ require file
19
+ end
20
+ end
21
+
16
22
  end
@@ -0,0 +1,23 @@
1
+ Description:
2
+ Stubs out a new cassandra migration.
3
+
4
+ Pass the migration name, either CamelCased or under_scored. A migration
5
+ class is generated in db/cassandra_migrate, prefixed by a timestamp of the
6
+ current date and time
7
+
8
+ Example:
9
+ rails generate migration create_tweets
10
+
11
+ This will create:
12
+ db/cassandra_migrate/20080514090912_create_tweets.rb
13
+
14
+ Inside this file we'll have a migration ready for completion:
15
+ class CreateTweets << CassandraMigrations::Migration
16
+ def up
17
+
18
+ end
19
+
20
+ def down
21
+
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ class CassandraMigrationGenerator < Rails::Generators::Base
2
+ source_root File.expand_path('templates', File.dirname(__FILE__))
3
+
4
+ argument :migration_name, :type => :string
5
+
6
+ # Interpolates template and creates migration in the application
7
+ #
8
+ # Any public method in the generator is run automatically when
9
+ # the generator is run. To understand fully see
10
+ # http://asciicasts.com/episodes/218-making-generators-in-rails-3
11
+
12
+ def generate_migration
13
+ file_name = "#{Time.current.utc.strftime('%Y%m%d%H%M%S')}_#{migration_name.underscore}"
14
+ @migration_class_name = migration_name.camelize
15
+
16
+ template "empty_migration.rb.erb", "db/cassandra_migrate/#{file_name}.rb"
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ class <%= @migration_class_name %> < CassandraMigrations::Migration
2
+ def up
3
+
4
+ end
5
+
6
+ def down
7
+
8
+ end
9
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cassandra_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.pre4
4
+ version: 0.0.1.pre5
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -59,6 +59,22 @@ dependencies:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
61
  version: '3.2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: colorize
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.5'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.5'
62
78
  - !ruby/object:Gem::Dependency
63
79
  name: rspec
64
80
  requirement: !ruby/object:Gem::Requirement
@@ -94,22 +110,31 @@ dependencies:
94
110
  description: A gem to manage Cassandra database schema for Rails. This gem offers
95
111
  migrations and environment specific databases out-of-the-box for Rails users.
96
112
  email: guberthenrique@hotmail.com
97
- executables: []
113
+ executables:
114
+ - prepare_for_cassandra
98
115
  extensions: []
99
116
  extra_rdoc_files: []
100
117
  files:
101
118
  - lib/cassandra_migrations.rb
119
+ - lib/cassandra_migrations/migration.rb
102
120
  - lib/cassandra_migrations/migrator.rb
103
121
  - lib/cassandra_migrations/cassandra.rb
104
122
  - lib/cassandra_migrations/railtie.rb
105
123
  - lib/cassandra_migrations/cassandra/keyspace_operations.rb
106
124
  - lib/cassandra_migrations/cassandra/queries.rb
107
125
  - lib/cassandra_migrations/config.rb
126
+ - lib/cassandra_migrations/migration/table_operations.rb
127
+ - lib/cassandra_migrations/migration/column_operations.rb
128
+ - lib/cassandra_migrations/migration/table_definition.rb
108
129
  - lib/cassandra_migrations/railtie/initializer.rb
130
+ - lib/cassandra_migrations/railtie/generators/cassandra_migration/cassandra_migration_generator.rb
131
+ - lib/cassandra_migrations/railtie/generators/cassandra_migration/templates/empty_migration.rb.erb
132
+ - lib/cassandra_migrations/railtie/generators/cassandra_migration/USAGE
109
133
  - lib/cassandra_migrations/railtie/tasks.rake
110
134
  - lib/cassandra_migrations/errors.rb
111
135
  - spec/cassandra_migrations_spec.rb
112
136
  - spec/cassandra_migrations/cassandra_spec.rb
137
+ - bin/prepare_for_cassandra
113
138
  homepage: https://github.com/hsgubert/cassandra_migrations
114
139
  licenses:
115
140
  - MIT