sqrbl 0.1.0
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/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
|