-
Notifications
You must be signed in to change notification settings - Fork 49
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take this out. Use the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, sure I will. |
||
raise | ||
rescue StandardError | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
end | ||
|
||
def initialize(path) | ||
@path = path | ||
@version, snake_class_name = path.scan(%r{^.*/(\d+)_(.*)\.rb}).flatten | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
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 | ||
|
@@ -37,5 +43,22 @@ namespace :nobrainer do | |
task :reset => [:drop, :sync_schema_quiet, :seed] | ||
|
||
task :create => [:sync_schema] | ||
task :migrate => [:sync_schema] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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' | ||
|
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 |
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 likeadd_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!