Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements migration scripts #268

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gemfiles/rails4.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ gem 'eventmachine' if ENV['EM']
gem "activesupport", "< 5.0.0"
gem "activemodel", "< 5.0.0"

gem 'generator_spec'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be in the 'development' group? or we want it as a real dependency?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understood the way it work, Gemfile in a gem is just like add_development_dependency and therefore shouldn't be a real dependency.
Only the one declared in the gemspec file with add_dependency will be real dependency.

If that statement is wrong, then I should update it, of course!


group :development do
gem 'pry'
gem 'simplecov', :require => false
Expand Down
2 changes: 2 additions & 0 deletions gemfiles/rails5.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ gem 'eventmachine' if ENV['EM']
gem "activesupport", "< 6"
gem "activemodel", "< 6"

gem 'generator_spec'

group :development do
gem 'pry'
gem 'simplecov', :require => false
Expand Down
4 changes: 3 additions & 1 deletion gemfiles/rails6.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ gem 'eventmachine' if ENV['EM']
gem "activesupport", ">= 6.0.0"
gem "activemodel", ">= 6.0.0"

gem 'generator_spec'

group :development do
gem 'pry'
gem 'awesome_print', :require => false
gem 'pry'
zedtux marked this conversation as resolved.
Show resolved Hide resolved
gem 'simplecov', :require => false
end
1 change: 1 addition & 0 deletions lib/no_brainer/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class LostLock < RuntimeError; end
class LockInvalidOp < RuntimeError; end
class LockUnavailable < RuntimeError; end
class InvalidPolymorphicType < RuntimeError; end
class MigrationFailure < RuntimeError; end

class DocumentInvalid < RuntimeError
attr_accessor :instance
Expand Down
34 changes: 34 additions & 0 deletions lib/no_brainer/migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module NoBrainer
#
# Parent class of all migration scripts.
# This class is used by the NoBrainer::Migrator class.
#
class Migration
class NoImplementedError < StandardError; end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take this out. Use the NotImplementedError of ruby

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know Ruby had such a class, I will change that!


def migrate!
up
rescue StandardError
raise unless respond_to?(:down)

begin
down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Print an error message right before calling down with the exception, and the fact that you are running down. People need to know what's going on.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, sure I will.

raise
rescue StandardError
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem useful?

raise
end
end

def rollback!
down if respond_to?(:down)
rescue StandardError
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what's that rescue block gives you, can you explain?

raise
end

def up
raise NoImplementedError
end
end
end
125 changes: 125 additions & 0 deletions lib/no_brainer/migrator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# frozen_string_literal: true

module NoBrainer
#
# The Migrator class is used in order to execute a migration script and store
# the migration script "version" or timestap in a table allowing to run it
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timestap typo

# only once.
# The mechanism is heavily inspired by the ActiveRecord one.
#
# It supports both migrate and rollback.
#
class Migrator
def self.migrations_table_name
'nobrainer_migration_versions'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a document class for the migration table, similar to lib/no_brainer/lock.rb
You can call it MigrationState, and the table can be called 'nobrainer_migrations'
This should simplify your table manipulation logic.

end

def initialize(path)
@path = path
@version, snake_class_name = path.scan(%r{^.*/(\d+)_(.*)\.rb}).flatten

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

take out new line

@name = snake_class_name ? snake_class_name.camelize : nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use try(:camelize)

end

def migrate
return if version_exists?

require @path

announce 'migrating'

time = Benchmark.measure do
migration_script = @name.constantize.new
migration_script.migrate!
end

announce 'migrated (%.4fs)' % time.real

create_version!
end

def rollback
return unless version_exists?

announce 'reverting'

require @path

time = Benchmark.measure do
migration_script = @name.constantize.new
migration_script.rollback!
end

announce 'reverted (%.4fs)' % time.real

remove_version!
end

private

# Stollen from the ActiveRecord gem
def announce(message)
text = "#{@version} #{@name}: #{message}"
length = [0, 75 - text.length].max
puts "== #{text} #{'=' * length}"
end

def check_if_version_exists?
NoBrainer.run do |r|
r.table(self.class.migrations_table_name).filter({ version: @version })
end.first.present?
end

def create_migrations_table_if_needed!
return if migrations_table_exists?

create_migrations_table!
end

def create_migrations_table!
result = NoBrainer.run do |r|
r.table_create(self.class.migrations_table_name)
end

return if result['tables_created'] == 1

raise NoBrainer::Error::MigrationFailure,
'Something prevented from creating the ' \
"'#{self.class.migrations_table_name}' table."
end

def create_version!
result = NoBrainer.run do |r|
r.table(self.class.migrations_table_name).insert({ version: @version })
end

return true if result['inserted'] == 1

raise NoBrainer::Error::MigrationFailure,
"Something prevented from inserting the version '#{@version}'"
end

def migrations_table_exists?
NoBrainer.run(&:table_list).include?(self.class.migrations_table_name)
end

def remove_version!
result = NoBrainer.run do |r|
r.table(self.class.migrations_table_name)
.filter({ version: @version })
.delete
end

return true if result['deleted'] == 1

raise NoBrainer::Error::MigrationFailure,
"Something prevented from deleting the version '#{@version}'"
end

def version_exists?
create_migrations_table_if_needed!

check_if_version_exists?
end
end
end
25 changes: 24 additions & 1 deletion lib/no_brainer/railtie/database.rake
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# frozen_string_literal: true

def sorted_migration_script_pathes
Dir.glob(Rails.root.join('db', 'migrate', '*.rb')).sort
end

namespace :nobrainer do
desc 'Drop the database'
task :drop => :environment do
Expand Down Expand Up @@ -37,5 +43,22 @@ namespace :nobrainer do
task :reset => [:drop, :sync_schema_quiet, :seed]

task :create => [:sync_schema]
task :migrate => [:sync_schema]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we sync_schema still?


desc 'Run migration scripts'
task :migrate => :environment do
NoBrainer.sync_schema

sorted_migration_script_pathes.each do |script|
migrator = NoBrainer::Migrator.new(script)
migrator.migrate
end
end

desc 'Rollback the last migration script'
task :rollback => :environment do
sorted_migration_script_pathes.reverse.each do |script|
migrator = NoBrainer::Migrator.new(script)
break if migrator.rollback
end
end
end
3 changes: 2 additions & 1 deletion lib/nobrainer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module NoBrainer
# Code that is loaded through the DSL of NoBrainer should not be eager loaded.
autoload :Document, :IndexManager, :Loader, :Fork, :Geo, :SymbolDecoration
eager_autoload :Config, :Connection, :ConnectionManager, :Error,
:QueryRunner, :Criteria, :RQL, :Lock, :ReentrantLock, :Profiler, :System
:QueryRunner, :Criteria, :RQL, :Lock, :ReentrantLock, :Profiler,
:System, :Migrator, :Migration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should they be eager loaded? and not just loaded on demand ?


class << self
delegate :connection, :disconnect, :to => 'NoBrainer::ConnectionManager'
Expand Down
31 changes: 31 additions & 0 deletions lib/rails/generators/nobrainer/migration_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require 'rails/generators/nobrainer/namespace_fix'
require 'rails/generators/named_base'

module NoBrainer
module Generators
#
# Generates a migration script prefixed with the date and time of
# its creation.
#
class MigrationGenerator < Rails::Generators::NamedBase
extend NoBrainer::Generators::NamespaceFix
source_root File.expand_path('../templates', __dir__)

desc 'Generates a new migration script in ./db/migrate/'

check_class_collision

def create_model_file
template 'migration.rb', File.join('db', 'migrate', timestamped_filename)
end

hook_for :test_framework

def timestamped_filename
"#{Time.current.strftime('%Y%m%d%H%M%S')}_#{file_name}.rb"
end
end
end
end
7 changes: 7 additions & 0 deletions lib/rails/generators/templates/migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class <%= class_name %> < NoBrainer::Migration
def up
end

def down
end
end
Loading