sqrbl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/LICENSE.txt +621 -0
- data/README.txt +85 -0
- data/Rakefile +32 -0
- data/TODO.txt +11 -0
- data/bin/sqrbl +8 -0
- data/lib/core_exts/object.rb +44 -0
- data/lib/core_exts/symbol.rb +16 -0
- data/lib/sqrbl/base_migration_writer.rb +52 -0
- data/lib/sqrbl/call_stack.rb +21 -0
- data/lib/sqrbl/group.rb +42 -0
- data/lib/sqrbl/individual_migration_writer.rb +48 -0
- data/lib/sqrbl/migration.rb +62 -0
- data/lib/sqrbl/mixins/expects_block_with_new.rb +27 -0
- data/lib/sqrbl/mixins/has_todos.rb +65 -0
- data/lib/sqrbl/mixins/method_missing_delegation.rb +83 -0
- data/lib/sqrbl/step.rb +192 -0
- data/lib/sqrbl/step_pair.rb +56 -0
- data/lib/sqrbl/unified_migration_writer.rb +34 -0
- data/lib/sqrbl.rb +83 -0
- data/spec/README.txt +4 -0
- data/spec/functional/base_migration_writer_spec.rb +12 -0
- data/spec/functional/individual_migration_writer_spec.rb +172 -0
- data/spec/functional/unified_migration_writer_spec.rb +97 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/unit/group_spec.rb +85 -0
- data/spec/unit/migration_spec.rb +68 -0
- data/spec/unit/sqrbl_spec.rb +28 -0
- data/spec/unit/step_pair_spec.rb +110 -0
- data/spec/unit/step_spec.rb +154 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- metadata +115 -0
data/lib/sqrbl/step.rb
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
# This file is part of SQrbL, which is free software licensed under v3 of the GNU GPL.
|
2
|
+
# For more information, please see README.txt and LICENSE.txt in the project's root directory.
|
3
|
+
|
4
|
+
|
5
|
+
module Sqrbl
|
6
|
+
# The Step class is where most of the work of SQrbL takes place.
|
7
|
+
# All of the public methods on this class have side effects: they
|
8
|
+
# take the parameters given, tweak their format in some way, and
|
9
|
+
# use them to append data to the +output+ attribute.
|
10
|
+
#
|
11
|
+
# For example, the following code will cause the DELETE statement
|
12
|
+
# to appear in +output+:
|
13
|
+
#
|
14
|
+
# action "Drop imported organizational contacts" {
|
15
|
+
# 'DELETE FROM new_widgets WHERE note LIKE "Imported from old_widgets"'
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
# +action+ is the primary method you'll use to do the actual work,
|
19
|
+
# but there are a number of other helper methods that let you insert
|
20
|
+
# variously-formatted comments, and +insert_into+ helps DRY up some
|
21
|
+
# of the verbosity involved in writing INSERT statements.
|
22
|
+
class Step
|
23
|
+
attr_reader :step_pair, :output, :block
|
24
|
+
|
25
|
+
include Sqrbl::ExpectsBlockWithNew
|
26
|
+
include Sqrbl::MethodMissingDelegation
|
27
|
+
delegate_method_missing_to :step_pair
|
28
|
+
include HasTodos
|
29
|
+
|
30
|
+
# :stopdoc:
|
31
|
+
GiantWarningText = <<-EOF
|
32
|
+
## ## ### ####### ## ## ###### ## ## ##### ##
|
33
|
+
## ## ## ## ## ## ### ## ## ### ## ## ## ##
|
34
|
+
## ## ## ## ## ## #### ## ## #### ## ## ##
|
35
|
+
## # ## ####### ####### ## ## ## ## ## ## ## ## #### ##
|
36
|
+
## ### ## ## ## ## ## ## #### ## ## #### ## ## ##
|
37
|
+
#### #### ## ## ## ## ## ### ## ## ### ## ##
|
38
|
+
### ### ## ## ## ## ## ## ###### ## ## ##### ##
|
39
|
+
EOF
|
40
|
+
GiantWarningSeparator = '#' * GiantWarningText.split(/\n/).map(&:length).max
|
41
|
+
GiantWarningTemplate = "/*" +
|
42
|
+
"\n" + GiantWarningSeparator + "\n" +
|
43
|
+
"\n" + GiantWarningText +
|
44
|
+
"\n" + GiantWarningSeparator + "\n[[LineNum]]\n[[Message]]" +
|
45
|
+
"\n" + GiantWarningSeparator +
|
46
|
+
"\n" + "*/"
|
47
|
+
# :startdoc:
|
48
|
+
|
49
|
+
def initialize(step_pair, options = {}, &block)
|
50
|
+
@output = ''
|
51
|
+
@step_pair = step_pair
|
52
|
+
@block = lambda(&block)
|
53
|
+
|
54
|
+
eval_block_on_initialize(options)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Writes a commented-out Todo item to +output+.
|
58
|
+
#
|
59
|
+
# Also, creates a Todo object that may be used to create console
|
60
|
+
# output when building the *.sql output files (see HasTodos for
|
61
|
+
# more information).
|
62
|
+
def todo(message)
|
63
|
+
returning(super) do |todo|
|
64
|
+
todo_msg = "--> %s %s:\t%s" % ['todo'.upcase, caller_info(todo), todo.message]
|
65
|
+
write todo_msg
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Takes +message+ and writes it to +output+ surrounded by a large
|
70
|
+
# block-commented ASCII-art message that reads "WARNING!". The
|
71
|
+
# intent here is to create a large message that stands out when
|
72
|
+
# scrolling through the output or copy/pasting it into your SQL
|
73
|
+
# client. This message will also contain information pointing
|
74
|
+
# you to the place where +warning+ was called.
|
75
|
+
#
|
76
|
+
# For example, calling <tt>warning "Danger, Will Robinson!"</tt>
|
77
|
+
# from foo.rb on line 42 will append something like the following
|
78
|
+
# to +output+:
|
79
|
+
#
|
80
|
+
# /*
|
81
|
+
# #######################################################################
|
82
|
+
#
|
83
|
+
# ## ## ### ####### ## ## ###### ## ## ##### ##
|
84
|
+
# ## ## ## ## ## ## ### ## ## ### ## ## ## ##
|
85
|
+
# ## ## ## ## ## ## #### ## ## #### ## ## ##
|
86
|
+
# ## # ## ####### ####### ## ## ## ## ## ## ## ## #### ##
|
87
|
+
# ## ### ## ## ## ## ## ## #### ## ## #### ## ## ##
|
88
|
+
# #### #### ## ## ## ## ## ### ## ## ### ## ##
|
89
|
+
# ### ### ## ## ## ## ## ## ###### ## ## ##### ##
|
90
|
+
#
|
91
|
+
# #######################################################################
|
92
|
+
# (foo.rb, line 42)
|
93
|
+
# Danger, Will Robinson!
|
94
|
+
# #######################################################################
|
95
|
+
# */
|
96
|
+
#
|
97
|
+
# Also, creates a Todo object of subtype <tt>:warning</tt> that
|
98
|
+
# may be used to create console output when building the *.sql
|
99
|
+
# output files (see HasTodos for more information).
|
100
|
+
def warning(message)
|
101
|
+
returning(super) do |warn|
|
102
|
+
giant_warning = GiantWarningTemplate.gsub('[[LineNum]]', caller_info(warn)) \
|
103
|
+
.gsub('[[Message]]', warn.message)
|
104
|
+
write giant_warning
|
105
|
+
end
|
106
|
+
end
|
107
|
+
alias :danger_will_robinson! :warning
|
108
|
+
|
109
|
+
# Outputs a message preceded by the SQL line-comment prefix <tt>"-- "</tt>.
|
110
|
+
def comment(message)
|
111
|
+
write '-- ' + message
|
112
|
+
end
|
113
|
+
|
114
|
+
# Outputs a message surrounded by /* SQL block-comment delimiters */.
|
115
|
+
def block_comment(&block_returning_string)
|
116
|
+
write "/*\n%s\n*/" % indent(4, unindent(yield))
|
117
|
+
end
|
118
|
+
|
119
|
+
# Expects a message and a block whose return value is a string.
|
120
|
+
# Outputs a comment containing +message+, followed by the return value of the block.
|
121
|
+
def action(message, &block_returning_string)
|
122
|
+
comment(message)
|
123
|
+
write(unindent(block_returning_string.call))
|
124
|
+
end
|
125
|
+
|
126
|
+
# Helper method for DRYing up INSERT statements. Takes a table name
|
127
|
+
# and a hash that maps field names to SQL expressions, and builds an
|
128
|
+
# INSERT statement that populates those fields. For example:
|
129
|
+
#
|
130
|
+
# insert_into 'new_widgets', {
|
131
|
+
# :name => 'widget_name',
|
132
|
+
# :part_num => 'CONCAT("X_", part_number)',
|
133
|
+
# :note => '"Imported from old_widgets"',
|
134
|
+
# }
|
135
|
+
#
|
136
|
+
# will produce:
|
137
|
+
#
|
138
|
+
# INSERT INTO new_widgets (
|
139
|
+
# part_num,
|
140
|
+
# note,
|
141
|
+
# name
|
142
|
+
# )
|
143
|
+
# SELECT
|
144
|
+
# CONCAT("X_", part_number) AS part_num,
|
145
|
+
# "Imported from old_widgets" AS note,
|
146
|
+
# widget_name AS name
|
147
|
+
#
|
148
|
+
# <i>(Note that this only creates the INSERT INTO and SELECT clauses
|
149
|
+
# of a SQL statement.)</i>
|
150
|
+
def insert_into(table_name, map_from_fieldname_to_expr = {})
|
151
|
+
# Build the "INSERT INTO (foo, bar, baz)" clause
|
152
|
+
insert_fields = map_from_fieldname_to_expr.keys.join(",\n")
|
153
|
+
insert_clause = "INSERT INTO %s (\n%s\n)\n" % [table_name, indent(4, insert_fields)]
|
154
|
+
|
155
|
+
# Build the "SELECT FROM" clause
|
156
|
+
select_fields = map_from_fieldname_to_expr.map { |field_name, expr| "#{expr} AS #{field_name}" }.join(",\n")
|
157
|
+
select_clause = "SELECT\n" + indent(4, select_fields)
|
158
|
+
|
159
|
+
insert_clause + select_clause
|
160
|
+
end
|
161
|
+
|
162
|
+
# Write +text+ to +output+, followed by newlines.
|
163
|
+
# (This is used by most other methods on Step; it's made public
|
164
|
+
# in case you want to use it in a way I didn't anticipate.)
|
165
|
+
def write(text)
|
166
|
+
output << text + "\n\n"
|
167
|
+
end
|
168
|
+
|
169
|
+
protected
|
170
|
+
# Format caller information from the Todo for output.
|
171
|
+
def caller_info(todo)
|
172
|
+
"(#{todo.creating_file}, line #{todo.calling_line})"
|
173
|
+
end
|
174
|
+
|
175
|
+
# Indents +text+ by +n+ spaces.
|
176
|
+
def indent(n, text)
|
177
|
+
text.to_s.gsub(/^(.)/, (' ' * n) + '\1')
|
178
|
+
end
|
179
|
+
|
180
|
+
# If the first line of +text+ has leading whitespace, remove
|
181
|
+
# that same amount of leading whitespace from all lines in +text+.
|
182
|
+
#
|
183
|
+
# (This is an anal-retentiveness helper method: it lets you
|
184
|
+
# keep your SQL statements indented below the +action+ calls
|
185
|
+
# that append them to +output+, while keeping that extra
|
186
|
+
# indentation from appearing in your generated *.sql files.)
|
187
|
+
def unindent(text)
|
188
|
+
return text unless text =~ /^(\s+)/
|
189
|
+
text.gsub(Regexp.new("^" + $1), '')
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# This file is part of SQrbL, which is free software licensed under v3 of the GNU GPL.
|
2
|
+
# For more information, please see README.txt and LICENSE.txt in the project's root directory.
|
3
|
+
|
4
|
+
|
5
|
+
module Sqrbl
|
6
|
+
# Like the Group class, StepPair doesn't do much on its own.
|
7
|
+
# It's basically a container for two Step objects, which are created using
|
8
|
+
# +up+ and +down+. These two steps will usually, but not always, have a
|
9
|
+
# one-to-one correspondence in their actions. <i>(For example, a temp table
|
10
|
+
# may take a number of actions to create and populate, but removing it
|
11
|
+
# just takes a "DROP TABLE" statement.)</i>
|
12
|
+
#
|
13
|
+
# * The +up_step+ describes the actions necessary to move the source data one
|
14
|
+
# step closer to the target format.
|
15
|
+
# * The +down_step+ describes the actions necessary to undo the actions taken
|
16
|
+
# by the up_step.
|
17
|
+
#
|
18
|
+
# StepPair delegates +method_missing+ calls to its +group+ object.
|
19
|
+
# For more information, see MethodMissingDelegation.
|
20
|
+
class StepPair
|
21
|
+
attr_reader :group, :description, :block
|
22
|
+
attr_reader :up_step, :down_step
|
23
|
+
|
24
|
+
include Sqrbl::ExpectsBlockWithNew
|
25
|
+
include Sqrbl::MethodMissingDelegation
|
26
|
+
delegate_method_missing_to :group
|
27
|
+
include HasTodos
|
28
|
+
|
29
|
+
def initialize(group, description, options = {}, &block)
|
30
|
+
@group = group
|
31
|
+
@description = description
|
32
|
+
@block = lambda(&block)
|
33
|
+
|
34
|
+
eval_block_on_initialize(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a Step object, passing it the block.
|
38
|
+
def up(&block)
|
39
|
+
@up_step = Step.new(self, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create a Step object, passing it the block.
|
43
|
+
def down(&block)
|
44
|
+
@down_step = Step.new(self, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
# A StepPair is valid if both the up_step and the down_step have been created as Step objects.
|
48
|
+
def valid?
|
49
|
+
[up_step, down_step].all? { |step| step.kind_of?(Step) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def unix_name
|
53
|
+
Sqrbl.calculate_unix_name(description)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sqrbl
|
2
|
+
# Writes two files: output_directory/all_up.sql and output_directory/all_down.sql.
|
3
|
+
# * output_directory/all_up.sql contains all 'up' steps, in creation order.
|
4
|
+
# * output_directory/all_down.sql contains all 'down' steps, in reverse creation order.
|
5
|
+
class UnifiedMigrationWriter < BaseMigrationWriter
|
6
|
+
# Create all_up.sql and all_down.sql in output_directory.
|
7
|
+
def write!
|
8
|
+
ensure_dir_exists(output_directory)
|
9
|
+
write_file(up_file, all_up_steps_output)
|
10
|
+
write_file(down_file, all_down_steps_output)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Output from all 'up' steps, in creation order
|
14
|
+
def all_up_steps_output
|
15
|
+
migration.up_steps.map(&:output).join
|
16
|
+
end
|
17
|
+
|
18
|
+
# Output from all 'down' steps, in reverse creation order
|
19
|
+
def all_down_steps_output
|
20
|
+
migration.down_steps.reverse.map(&:output).join
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
# Pathname to all_up.sql
|
25
|
+
def up_file
|
26
|
+
File.join(output_directory, 'all_up.sql' )
|
27
|
+
end
|
28
|
+
|
29
|
+
# Pathname to all_down.sql
|
30
|
+
def down_file
|
31
|
+
File.join(output_directory, 'all_down.sql')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/sqrbl.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# This file is part of SQrbL, which is free software licensed under v3 of the GNU GPL.
|
2
|
+
# For more information, please see README.txt and LICENSE.txt in the project's root directory.
|
3
|
+
#
|
4
|
+
# Most of this was autogenerated by the 'bones' gem.
|
5
|
+
# The primary domain-specific method of interest here is Sqrbl.migration.
|
6
|
+
module Sqrbl
|
7
|
+
|
8
|
+
# :stopdoc:
|
9
|
+
VERSION = '0.1.0'
|
10
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
11
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
12
|
+
# :startdoc:
|
13
|
+
|
14
|
+
# Returns the version string for the library.
|
15
|
+
#
|
16
|
+
def self.version
|
17
|
+
VERSION
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the library path for the module. If any arguments are given,
|
21
|
+
# they will be joined to the end of the libray path using
|
22
|
+
# <tt>File.join</tt>.
|
23
|
+
#
|
24
|
+
def self.libpath( *args )
|
25
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the lpath for the module. If any arguments are given,
|
29
|
+
# they will be joined to the end of the path using
|
30
|
+
# <tt>File.join</tt>.
|
31
|
+
#
|
32
|
+
def self.path( *args )
|
33
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Utility method used to require all files ending in .rb that lie in the
|
37
|
+
# directory below this file that has the same name as the filename passed
|
38
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
39
|
+
# the _filename_ does not have to be equivalent to the directory.
|
40
|
+
#
|
41
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
42
|
+
dir ||= ::File.basename(fname, '.*')
|
43
|
+
search_me = ::File.expand_path(
|
44
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
45
|
+
|
46
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Convenience method: builds and writes a migration in one go.
|
50
|
+
def self.migration(&block)
|
51
|
+
returning(build_migration(&block)) do |mig|
|
52
|
+
write_migration!(mig)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Convenience method: Calls Sqrbl::Migration.build and passes it the given block.
|
57
|
+
def self.build_migration(&block)
|
58
|
+
Sqrbl::Migration.build(&block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Convenience method: Write a Migration using all subclasses of BaseMigrationWriter.
|
62
|
+
def self.write_migration!(migration)
|
63
|
+
BaseMigrationWriter.subclasses.each do |writer_class|
|
64
|
+
writer_class.write_migration!(migration)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the string, in lower case, with whitespace converted to underscores
|
69
|
+
# and all non-alphanumeric characters removed.
|
70
|
+
def self.calculate_unix_name(string)
|
71
|
+
string.gsub(/\s+/, '_').downcase.gsub(/[^a-z0-9_]/, '')
|
72
|
+
end
|
73
|
+
|
74
|
+
end # module Sqrbl
|
75
|
+
|
76
|
+
#--
|
77
|
+
# TODO: figure out how to do autoload on const_missing
|
78
|
+
#++
|
79
|
+
Sqrbl.require_all_libs_relative_to(__FILE__, 'core_exts')
|
80
|
+
Sqrbl.require_all_libs_relative_to(__FILE__, 'sqrbl/mixins')
|
81
|
+
Sqrbl.require_all_libs_relative_to(__FILE__)
|
82
|
+
|
83
|
+
# EOF
|
data/spec/README.txt
ADDED
@@ -0,0 +1,4 @@
|
|
1
|
+
Spec notes:
|
2
|
+
|
3
|
+
- When you see "foo.should_receive(:bar).with(self)" inside a block, it's probably a hack that takes advantage of the fact that the block is being instance_eval'd somewhere else.
|
4
|
+
- When you see "foo.should_receive(:bar).with(some_args).and_yield", the and_yield means that the #bar method on foo must be sent a block parameter.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[.. spec_helper])
|
2
|
+
|
3
|
+
include Sqrbl
|
4
|
+
|
5
|
+
describe BaseMigrationWriter do
|
6
|
+
it "should know what its subclasses are" do
|
7
|
+
BaseMigrationWriter.subclasses.should == [
|
8
|
+
IndividualMigrationWriter,
|
9
|
+
UnifiedMigrationWriter,
|
10
|
+
]
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[.. spec_helper])
|
2
|
+
|
3
|
+
include Sqrbl
|
4
|
+
|
5
|
+
describe IndividualMigrationWriter do
|
6
|
+
before(:each) do
|
7
|
+
@mig = Sqrbl.build_migration do
|
8
|
+
group 'Group one' do
|
9
|
+
step 'Step one' do
|
10
|
+
up { write 'Step one up' }
|
11
|
+
down { write 'Step one down' }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@base_dir = File.expand_path(File.join(File.dirname(__FILE__), 'sql'))
|
17
|
+
@writer = IndividualMigrationWriter.new(@mig)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have a output_directory attribute" do
|
21
|
+
@writer.output_directory.should == @base_dir
|
22
|
+
end
|
23
|
+
|
24
|
+
describe :write! do
|
25
|
+
it "should clear the sql/up and sql/down directories (yes, this is dangerous)" do
|
26
|
+
FileUtils.should_receive(:rm_rf).with(File.join(File.expand_path(@base_dir), 'up') )
|
27
|
+
FileUtils.should_receive(:rm_rf).with(File.join(File.expand_path(@base_dir), 'down'))
|
28
|
+
stub_out_file_creation
|
29
|
+
@writer.write!
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should NOT clear the sql/up and sql/down directories if passed :safe_mode => true" do
|
33
|
+
FileUtils.should_not_receive(:rm_rf)
|
34
|
+
stub_out_file_creation
|
35
|
+
@writer.write!(:safe_mode => true)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "for a migration with two groups and three steps" do
|
40
|
+
before(:each) do
|
41
|
+
@mig = Sqrbl.build_migration do
|
42
|
+
group 'Group one' do
|
43
|
+
step 'Step one' do
|
44
|
+
up { write 'Step one up' }
|
45
|
+
down { write 'Step one down' }
|
46
|
+
end
|
47
|
+
step 'Step two' do
|
48
|
+
up { write 'Step two up' }
|
49
|
+
down { write 'Step two down'}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
group 'Group two' do
|
53
|
+
step 'Step three' do
|
54
|
+
up { write 'Step three up' }
|
55
|
+
down { write 'Step three down' }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
@base_dir = File.expand_path(File.join(File.dirname(__FILE__), 'sql'))
|
61
|
+
@writer = IndividualMigrationWriter.new(@mig)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should create a separate subdirectory in [output_directory]/up for each group" do
|
65
|
+
@mig.groups.each_with_index do |group, idx|
|
66
|
+
group_dir = "#{idx + 1}_#{group.unix_name}"
|
67
|
+
up_subdir = File.join(@writer.output_directory, 'up', group_dir)
|
68
|
+
down_subdir = File.join(@writer.output_directory, 'down', group_dir)
|
69
|
+
File .should_receive(:directory?).with(up_subdir ).and_return(false)
|
70
|
+
File .should_receive(:directory?).with(down_subdir).and_return(false)
|
71
|
+
FileUtils.should_receive(:makedirs).with(up_subdir )
|
72
|
+
FileUtils.should_receive(:makedirs).with(down_subdir)
|
73
|
+
end
|
74
|
+
stub_out_file_creation
|
75
|
+
@writer.write!
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should create a separate file for each step, in the subdirectory for that step's group" do
|
79
|
+
@mig.groups.each_with_index do |group, idx|
|
80
|
+
group_dir = "#{idx + 1}_#{group.unix_name}"
|
81
|
+
up_subdir = File.join(@writer.output_directory, 'up', group_dir)
|
82
|
+
down_subdir = File.join(@writer.output_directory, 'down', group_dir)
|
83
|
+
group.steps.each_with_index do |step, idx|
|
84
|
+
up_step_filename = File.join(up_subdir, "#{idx + 1}_#{step.up_step.unix_name }.sql")
|
85
|
+
down_step_filename = File.join(down_subdir, "#{idx + 1}_#{step.down_step.unix_name}.sql")
|
86
|
+
|
87
|
+
@writer.should_receive(:write_file).with(up_step_filename, step.up_step.output )
|
88
|
+
@writer.should_receive(:write_file).with(down_step_filename, step.down_step.output)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
stub_out_file_creation
|
92
|
+
@writer.write!
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "for a migration with 10 groups" do
|
97
|
+
before(:each) do
|
98
|
+
@mig = Sqrbl.build_migration do
|
99
|
+
group 'Group one' do
|
100
|
+
step 'Step one' do
|
101
|
+
up { write 'Step one up' }
|
102
|
+
down { write 'Step one down' }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
group 'Group two' do
|
106
|
+
step 'Step two' do
|
107
|
+
up { write 'Step two up' }
|
108
|
+
down { write 'Step two down'}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
group 'Group three' do
|
112
|
+
step 'Step three' do
|
113
|
+
up { write 'Step three up' }
|
114
|
+
down { write 'Step three down' }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
group 'Group four' do
|
118
|
+
step 'Step four' do
|
119
|
+
up { write 'Step four up' }
|
120
|
+
down { write 'Step four down' }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
group 'Group five' do
|
124
|
+
step 'Step five' do
|
125
|
+
up { write 'Step five up' }
|
126
|
+
down { write 'Step five down' }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
group 'Group six' do
|
130
|
+
step 'Step six' do
|
131
|
+
up { write 'Step six up' }
|
132
|
+
down { write 'Step six down' }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
group 'Group seven' do
|
136
|
+
step 'Step seven' do
|
137
|
+
up { write 'Step seven up' }
|
138
|
+
down { write 'Step seven down' }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
group 'Group eight' do
|
142
|
+
step 'Step eight' do
|
143
|
+
up { write 'Step eight up' }
|
144
|
+
down { write 'Step eight down' }
|
145
|
+
end
|
146
|
+
end
|
147
|
+
group 'Group nine' do
|
148
|
+
step 'Step nine' do
|
149
|
+
up { write 'Step nine up' }
|
150
|
+
down { write 'Step nine down' }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
group 'Group ten' do
|
154
|
+
step 'Step ten' do
|
155
|
+
up { write 'Step ten up' }
|
156
|
+
down { write 'Step ten down' }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
@writer = IndividualMigrationWriter.new(@mig)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should left-fill the group numbers with zeros" do
|
165
|
+
group_ten_up_dir = File.join(@writer.output_directory, 'up', '10_group_ten')
|
166
|
+
File.should_receive(:directory?).with(group_ten_up_dir).and_return(false)
|
167
|
+
FileUtils.should_receive(:makedirs).with(group_ten_up_dir).once
|
168
|
+
stub_out_file_creation
|
169
|
+
@writer.write!
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[.. spec_helper])
|
2
|
+
|
3
|
+
include Sqrbl
|
4
|
+
|
5
|
+
describe UnifiedMigrationWriter do
|
6
|
+
describe "for a migration with #output_directory set" do
|
7
|
+
it "should set #output_directory from the migration's #output_directory" do
|
8
|
+
mig = mock('Migration', :output_directory => '/path/to/sql', :creating_file => nil)
|
9
|
+
writer = UnifiedMigrationWriter.new(mig)
|
10
|
+
writer.output_directory.should == mig.output_directory
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "for a migration with output_directory blank" do
|
15
|
+
it "should set #output_directory using the dirname of the migration's #creating_file, plus /sql" do
|
16
|
+
mig = mock('Migration', :output_directory => nil, :creating_file => '/path/to/some/sqrbl_file.rb')
|
17
|
+
writer = UnifiedMigrationWriter.new(mig)
|
18
|
+
writer.output_directory.should == '/path/to/some/sql'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "for a migration with two groups and three steps" do
|
23
|
+
before(:each) do
|
24
|
+
@mig = Sqrbl.build_migration do
|
25
|
+
group 'Group one' do
|
26
|
+
step 'Step one' do
|
27
|
+
up { write 'Step one up' }
|
28
|
+
down { write 'Step one down' }
|
29
|
+
end
|
30
|
+
step 'Step two' do
|
31
|
+
up { write 'Step two up' }
|
32
|
+
down { write 'Step two down'}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
group 'Group two' do
|
36
|
+
step 'Step three' do
|
37
|
+
up { write 'Step three up' }
|
38
|
+
down { write 'Step three down' }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@base_dir = File.expand_path(File.join(File.dirname(__FILE__), 'sql'))
|
44
|
+
@writer = UnifiedMigrationWriter.new(@mig)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not create the target directory if it already exists" do
|
48
|
+
File.should_receive(:directory?).with(@base_dir).and_return(true)
|
49
|
+
FileUtils.should_not_receive(:makedirs).with(@base_dir)
|
50
|
+
stub_out_file_creation
|
51
|
+
@writer.write!
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should create the target directory if it does not exist" do
|
55
|
+
File.should_receive(:directory?).with(@base_dir).and_return(false)
|
56
|
+
FileUtils.should_receive(:makedirs).with(@base_dir)
|
57
|
+
stub_out_file_creation
|
58
|
+
@writer.write!
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should create a file 'sql/all_up.sql' in the target directory" do
|
62
|
+
File.should_receive(:open).with(File.join(@base_dir, 'all_up.sql'), 'w')
|
63
|
+
stub_out_file_creation
|
64
|
+
@writer.write!
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should combine the contents of all up steps in order" do
|
68
|
+
@writer.all_up_steps_output.should == @mig.up_steps.map(&:output).join
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should write the contents of all up steps to the all_up.sql file" do
|
72
|
+
mock_file = mock('File')
|
73
|
+
File.should_receive(:open).with(File.join(@base_dir, 'all_up.sql'), 'w').and_yield(mock_file)
|
74
|
+
mock_file.should_receive(:<<).with(@writer.all_up_steps_output)
|
75
|
+
stub_out_file_creation
|
76
|
+
@writer.write!
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should create a file 'sql/all_down.sql' in the target directory" do
|
80
|
+
File.should_receive(:open).with(File.join(@base_dir, 'all_down.sql'), 'w')
|
81
|
+
stub_out_file_creation
|
82
|
+
@writer.write!
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should combine the contents of all down steps in REVERSE order" do
|
86
|
+
@writer.all_down_steps_output.should == @mig.down_steps.reverse.map(&:output).join
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should write the contents of all down steps to the all_down.sql file" do
|
90
|
+
mock_file = mock('File')
|
91
|
+
File.should_receive(:open).with(File.join(@base_dir, 'all_down.sql'), 'w').and_yield(mock_file)
|
92
|
+
mock_file.should_receive(:<<).with(@writer.all_down_steps_output)
|
93
|
+
stub_out_file_creation
|
94
|
+
@writer.write!
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# This file is part of SQrbL, which is free software licensed under v3 of the GNU GPL.
|
2
|
+
# For more information, please see README.txt and LICENSE.txt in the project's root directory.
|
3
|
+
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'spec'
|
7
|
+
|
8
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib sqrbl]))
|
9
|
+
|
10
|
+
Spec::Runner.configure do |config|
|
11
|
+
# == Mock Framework
|
12
|
+
#
|
13
|
+
# RSpec uses its own mocking framework by default. If you prefer to
|
14
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
15
|
+
#
|
16
|
+
# config.mock_with :mocha
|
17
|
+
# config.mock_with :flexmock
|
18
|
+
# config.mock_with :rr
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Stub out the various things that the migration writers do.
|
23
|
+
# Call this after defining the mocks you care about (because mocks created first take precedence).
|
24
|
+
# Then, calling #write! shouldn't actually touch the filesystem.
|
25
|
+
def stub_out_file_creation
|
26
|
+
File .should_receive(:directory?).with(any_args).any_number_of_times.and_return(true)
|
27
|
+
FileUtils.should_receive(:makedirs) .with(any_args).any_number_of_times
|
28
|
+
File .should_receive(:open) .with(any_args).any_number_of_times.and_return(mock('File'))
|
29
|
+
end
|
30
|
+
|
31
|
+
# EOF
|