sqrbl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/History.txt +4 -0
  2. data/LICENSE.txt +621 -0
  3. data/README.txt +85 -0
  4. data/Rakefile +32 -0
  5. data/TODO.txt +11 -0
  6. data/bin/sqrbl +8 -0
  7. data/lib/core_exts/object.rb +44 -0
  8. data/lib/core_exts/symbol.rb +16 -0
  9. data/lib/sqrbl/base_migration_writer.rb +52 -0
  10. data/lib/sqrbl/call_stack.rb +21 -0
  11. data/lib/sqrbl/group.rb +42 -0
  12. data/lib/sqrbl/individual_migration_writer.rb +48 -0
  13. data/lib/sqrbl/migration.rb +62 -0
  14. data/lib/sqrbl/mixins/expects_block_with_new.rb +27 -0
  15. data/lib/sqrbl/mixins/has_todos.rb +65 -0
  16. data/lib/sqrbl/mixins/method_missing_delegation.rb +83 -0
  17. data/lib/sqrbl/step.rb +192 -0
  18. data/lib/sqrbl/step_pair.rb +56 -0
  19. data/lib/sqrbl/unified_migration_writer.rb +34 -0
  20. data/lib/sqrbl.rb +83 -0
  21. data/spec/README.txt +4 -0
  22. data/spec/functional/base_migration_writer_spec.rb +12 -0
  23. data/spec/functional/individual_migration_writer_spec.rb +172 -0
  24. data/spec/functional/unified_migration_writer_spec.rb +97 -0
  25. data/spec/spec_helper.rb +31 -0
  26. data/spec/unit/group_spec.rb +85 -0
  27. data/spec/unit/migration_spec.rb +68 -0
  28. data/spec/unit/sqrbl_spec.rb +28 -0
  29. data/spec/unit/step_pair_spec.rb +110 -0
  30. data/spec/unit/step_spec.rb +154 -0
  31. data/tasks/ann.rake +80 -0
  32. data/tasks/bones.rake +20 -0
  33. data/tasks/gem.rake +201 -0
  34. data/tasks/git.rake +40 -0
  35. data/tasks/notes.rake +27 -0
  36. data/tasks/post_load.rake +34 -0
  37. data/tasks/rdoc.rake +51 -0
  38. data/tasks/rubyforge.rake +55 -0
  39. data/tasks/setup.rb +292 -0
  40. data/tasks/spec.rake +54 -0
  41. data/tasks/svn.rake +47 -0
  42. data/tasks/test.rake +40 -0
  43. data/tasks/zentest.rake +36 -0
  44. 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
@@ -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