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.
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