sequel-inline_schema 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6037f75d98e75ad634497e1000fdf8cac67cd388a366dc01d73e7a4f00897f15
4
+ data.tar.gz: 8c6d0fe5f0146d296e85a3e074cf28394e803104c93253e1f200c04274da3ba5
5
+ SHA512:
6
+ metadata.gz: d5bf204ce162b8dae4e8780202f5031c2c7a48bf2f3f3eaa18552e2a346c9096a6c046a2e6c5232eb3f3f175314e332fa95d51c11a29711f848135d7f7701dd9
7
+ data.tar.gz: 2eed48961ec313876ecc72aa512d53f7cdca5ab0792fe93309c01d359d8dbe72489f02ce91e5250769580b1f00a8cae25f449219b77a5b8d8c207332605fc551
@@ -0,0 +1 @@
1
+ ����&��ƒ��sȻc�9�&�gD X��n��%���i��,�,����.|���>E�Cs��*���&36h'���J����S5��|4j
@@ -0,0 +1,3 @@
1
+ ���Q.���e��Đ�xzEm��E�aQ��M'�5"�-`���࿝��;w���C��?�[b��R�Z���m��v��`� 2 �2�.�I��4�ң�~h� B:��;EL�C�=�P�:j�������T��j}�ɵuz�{�`p�dO��?�����Y�X�&!YQ
2
+ ����v�a��Lk��y:\D�e�'� O���لR
3
+ �n99*�ԃ�ĽU�k�q�m��]5���W�u���ZzT.�g�S�t��J=���Y��?��a�{��Н(���O�M6�-{�;).�=�"�'ϳ�/c+�L ����T�z�Ȋ�٘���U?��K�� ݶ����q+�K7�VX,7C���?HM��a`K&Wv�D�
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ README.md
3
+ ChangeLog.md
4
+
5
+ LICENSE.txt
@@ -0,0 +1,14 @@
1
+ # http://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ # Unix-style newlines with a newline ending every file
7
+ [*]
8
+ end_of_line = lf
9
+ insert_final_newline = true
10
+
11
+ # Tab indentation
12
+ [**.*]
13
+ indent_style = tab
14
+
@@ -0,0 +1,16 @@
1
+ --- !ruby/object:RDoc::Options
2
+ encoding: UTF-8
3
+ static_path: []
4
+ rdoc_include:
5
+ - .
6
+ charset: UTF-8
7
+ exclude:
8
+ hyperlink_all: false
9
+ line_numbers: false
10
+ main_page: README.md
11
+ markup: markdown
12
+ show_hash: false
13
+ tab_width: 8
14
+ title: sequel-plugins-inline_schema Documentation
15
+ visibility: :protected
16
+ webcvs:
@@ -0,0 +1,9 @@
1
+ # Simplecov config
2
+
3
+ SimpleCov.start do
4
+ add_filter 'spec'
5
+ add_filter 'integration'
6
+ add_group "Needing tests" do |file|
7
+ file.covered_percent < 90
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ 2018-07-10 Michael Granger <ged@FaerieMUD.org>
2
+
3
+ * .hgtags:
4
+ Added tag v0.0.1 for changeset 5f6554f4bd40
5
+ [8fe6f86ec7c7] [tip]
6
+
7
+ * .hgsigs:
8
+ Added signature for changeset f94a7b19d4de
9
+ [5f6554f4bd40] [v0.0.1]
10
+
11
+ * .hgignore, History.md, README.md, Rakefile, sequel-
12
+ inline_schema.gemspec:
13
+ Prep for first release.
14
+ [f94a7b19d4de]
15
+
16
+ 2017-09-28 Michael Granger <ged@FaerieMUD.org>
17
+
18
+ * lib/sequel/plugins/inline_migrations.rb,
19
+ lib/sequel/plugins/inline_schema.rb:
20
+ Fix documentation formatting, add some more details about migrations
21
+ [f7de01d27012] [github/master]
22
+
23
+ 2017-09-27 Michael Granger <ged@FaerieMUD.org>
24
+
25
+ * README.md:
26
+ Fix README links.
27
+ [81085384e8eb]
28
+
29
+ * lib/sequel/plugins/inline_schema.rb,
30
+ spec/sequel/plugins/inline_schema_spec.rb:
31
+ More documentation updates, add drop_table hooks.
32
+ [dc2231fc945a]
33
+
34
+ * .hgignore, README.md, lib/sequel/plugins/inline_migrations.rb,
35
+ lib/sequel/plugins/inline_schema.rb:
36
+ Documentation fixes/additions.
37
+ [4783fffe2ab9]
38
+
39
+ * .document, .editorconfig, .gems, .hgignore, .pryrc, .rdoc_options,
40
+ .ruby-gemset, .ruby-version, .simplecov, Gemfile, History.md,
41
+ LICENSE.txt, Manifest.txt, README.md, Rakefile, certs/ged.pem,
42
+ lib/sequel/inline_schema.rb,
43
+ lib/sequel/plugins/inline_migrations.rb,
44
+ lib/sequel/plugins/inline_schema.rb, sequel-inline_schema.gemspec,
45
+ spec/sequel/plugins/inline_migrations_spec.rb,
46
+ spec/sequel/plugins/inline_schema_spec.rb, spec/spec_helper.rb:
47
+ Initial implementation
48
+ [fe2f291518ce]
@@ -0,0 +1,4 @@
1
+ ## v0.0.1 [2018-07-19] Michael Granger <ged@FaerieMUD.org>
2
+
3
+ Initial release.
4
+
@@ -0,0 +1,26 @@
1
+ This plugin uses code from the Sequel project under the MIT License:
2
+
3
+ Copyright (c) 2007-2008 Sharon Rosner
4
+ Copyright (c) 2008-2017 Jeremy Evans
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to
8
+ deal in the Software without restriction, including without limitation the
9
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
+ sell copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ The rest is licensed under the same terms, but:
24
+
25
+ Copyright (c) 2017, Michael Granger
26
+
@@ -0,0 +1,16 @@
1
+ .document
2
+ .editorconfig
3
+ .rdoc_options
4
+ .simplecov
5
+ ChangeLog
6
+ History.md
7
+ LICENSE.txt
8
+ Manifest.txt
9
+ README.md
10
+ Rakefile
11
+ lib/sequel/inline_schema.rb
12
+ lib/sequel/plugins/inline_migrations.rb
13
+ lib/sequel/plugins/inline_schema.rb
14
+ spec/sequel/plugins/inline_migrations_spec.rb
15
+ spec/sequel/plugins/inline_schema_spec.rb
16
+ spec/spec_helper.rb
@@ -0,0 +1,85 @@
1
+ # sequel-inline_schema
2
+
3
+ home
4
+ : http://bitbucket.org/ged/sequel-inlineschema
5
+
6
+ github
7
+ : https://github.com/ged/sequel-inlineschema
8
+
9
+ docs
10
+ : http://deveiate.org/code/sequel-inline_schema
11
+
12
+
13
+ ## Description
14
+
15
+ This is a set of plugins for Sequel for declaring a model's table schema and
16
+ any migrations in the class itself (similar to the legacy `schema` plugin).
17
+
18
+ It has only really been tested with PostgreSQL, but patches that make it more generic are welcomed.
19
+
20
+ The two plugins are:
21
+
22
+ * Sequel::Plugins::InlineSchema
23
+ * Sequel::Plugins::InlineMigrations
24
+
25
+ Examples and usage documentation are included there.
26
+
27
+
28
+ ## Prerequisites
29
+
30
+ * Ruby >= 2.4
31
+ * Sequel >= 5.0
32
+
33
+
34
+ ## Installation
35
+
36
+ $ gem install sequel-inline_schema
37
+
38
+
39
+ ## Contributing
40
+
41
+ You can check out the current development source with Mercurial via its
42
+ [project page][bitbucket]. Or if you prefer Git, via [its Github
43
+ mirror][github].
44
+
45
+ After checking out the source, run:
46
+
47
+ $ rake newb
48
+
49
+ This task will install any missing dependencies, run the tests/specs,
50
+ and generate the API documentation.
51
+
52
+
53
+ ## License
54
+
55
+ This plugin uses code from the Sequel project under the MIT License:
56
+
57
+ Copyright (c) 2007-2008 Sharon Rosner
58
+ Copyright (c) 2008-2017 Jeremy Evans
59
+
60
+ Permission is hereby granted, free of charge, to any person obtaining a copy
61
+ of this software and associated documentation files (the "Software"), to
62
+ deal in the Software without restriction, including without limitation the
63
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
64
+ sell copies of the Software, and to permit persons to whom the Software is
65
+ furnished to do so, subject to the following conditions:
66
+
67
+ The above copyright notice and this permission notice shall be included in
68
+ all copies or substantial portions of the Software.
69
+
70
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
71
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
72
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
73
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
74
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
75
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
76
+
77
+ The rest is licensed under the same terms, but:
78
+
79
+ Copyright (c) 2017-2018, Michael Granger
80
+
81
+
82
+
83
+ [bitbucket]: http://bitbucket.org/ged/sequel-inlineschema
84
+ [github]: https://github.com/ged/sequel-inlineschema
85
+
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env rake
2
+
3
+ begin
4
+ require 'hoe'
5
+ rescue LoadError
6
+ abort "This Rakefile requires hoe (gem install hoe)"
7
+ end
8
+
9
+ GEMSPEC = 'sequel-inline_schema.gemspec'
10
+
11
+
12
+ Hoe.plugin :mercurial
13
+ Hoe.plugin :signing
14
+ Hoe.plugin :deveiate
15
+
16
+ Hoe.plugins.delete :rubyforge
17
+
18
+ hoespec = Hoe.spec 'sequel-inline_schema' do |spec|
19
+
20
+ spec.readme_file = 'README.md'
21
+ spec.history_file = 'History.md'
22
+ spec.urls = {
23
+ home: 'http://bitbucket.org/ged/sequel-inline_schema',
24
+ code: 'http://bitbucket.org/ged/sequel-inline_schema',
25
+ docs: 'http://deveiate.org/code/sequel-inline_schema',
26
+ github: 'http://github.com/ged/sequel-inline_schema',
27
+ }
28
+
29
+ spec.extra_rdoc_files = FileList[ '*.rdoc', '*.md' ]
30
+ spec.license 'BSD-3-Clause'
31
+
32
+ spec.developer 'Michael Granger', 'ged@FaerieMUD.org'
33
+
34
+ spec.dependency 'sequel', '~> 5.0'
35
+
36
+ spec.dependency 'hoe-deveiate', '~> 0.9', :developer
37
+ spec.dependency 'simplecov', '~> 0.13', :developer
38
+ spec.dependency 'rdoc-generator-fivefish', '~> 0.3', :developer
39
+
40
+ spec.require_ruby_version( '>=2.4.0' )
41
+ spec.hg_sign_tags = true if spec.respond_to?( :hg_sign_tags= )
42
+ spec.check_history_on_release = true if spec.respond_to?( :check_history_on_release= )
43
+
44
+ spec.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
45
+ end
46
+
47
+
48
+ ENV['VERSION'] ||= hoespec.spec.version.to_s
49
+
50
+ # Run the tests before checking in
51
+ task 'hg:precheckin' => [ :check_history, :check_manifest, :gemspec, :spec ]
52
+
53
+ task :test => :spec
54
+
55
+ # Rebuild the ChangeLog immediately before release
56
+ task :prerelease => 'ChangeLog'
57
+ CLOBBER.include( 'ChangeLog' )
58
+
59
+ desc "Build a coverage report"
60
+ task :coverage do
61
+ ENV["COVERAGE"] = 'yes'
62
+ Rake::Task[:spec].invoke
63
+ end
64
+ CLOBBER.include( 'coverage' )
65
+
66
+
67
+ # Use the fivefish formatter for docs generated from development checkout
68
+ if File.directory?( '.hg' )
69
+ require 'rdoc/task'
70
+
71
+ Rake::Task[ 'docs' ].clear
72
+ RDoc::Task.new( 'docs' ) do |rdoc|
73
+
74
+ rdoc.markup = 'markdown'
75
+ rdoc.main = "README.md"
76
+ rdoc.rdoc_files.include( "*.md", "ChangeLog", "lib/**/*.rb" )
77
+
78
+ rdoc.generator = :fivefish
79
+ rdoc.title = 'sequel-inline_schema'
80
+ rdoc.rdoc_dir = 'doc'
81
+ end
82
+ end
83
+
84
+ task :gemspec => GEMSPEC
85
+ file GEMSPEC => [ __FILE__, 'Manifest.txt' ]
86
+ task GEMSPEC do |task|
87
+ spec = $hoespec.spec
88
+ spec.files.delete( '.gemtest' )
89
+ spec.signing_key = nil
90
+ spec.cert_chain = ['certs/ged.pem']
91
+ spec.version = "#{spec.version.bump}.0.pre#{Time.now.strftime("%Y%m%d%H%M%S")}"
92
+ File.open( task.name, 'w' ) do |fh|
93
+ fh.write( spec.to_ruby )
94
+ end
95
+ end
96
+ CLOBBER.include( GEMSPEC.to_s )
97
+
98
+ task :default => :gemspec
99
+
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'sequel'
5
+ require 'sequel/plugins/inline_schema'
6
+
7
+ module Sequel::InlineSchema
8
+
9
+ # Package version
10
+ VERSION = '0.0.1'
11
+
12
+ # Version control revision
13
+ REVISION = %q$Revision: fe2f291518ce $
14
+
15
+ end # Sequel::InlineSchema
16
+
@@ -0,0 +1,475 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sequel'
4
+ require 'sequel/model'
5
+
6
+ Sequel.extension( :migration )
7
+
8
+
9
+ # A plugin for Sequel::Model that allows migrations for the model to be defined
10
+ # directly in the class declaration. It uses the `inline_schema` plugin
11
+ # internally, and will add it for you if necessary.
12
+ #
13
+ # ## Example
14
+ #
15
+ # Define a base (abstract) model class:
16
+ #
17
+ # ```
18
+ # # lib/acme/model.rb
19
+ # module Acme
20
+ # Model = Class.new( Sequel::Model )
21
+ # Model.def_Model( Acme )
22
+ #
23
+ # class Model
24
+ # plugin :inline_schema
25
+ # plugin :inline_migrations
26
+ # end
27
+ # end
28
+ # ```
29
+ #
30
+ # Defining a model class with two migrations:
31
+ #
32
+ # ```
33
+ # # lib/acme/vendor.rb
34
+ # require 'acme/model'
35
+ #
36
+ # class Acme::Vendor < Acme::Model( :vendor )
37
+ #
38
+ # # The schema should always be kept up-to-date. I.e., it should be
39
+ # # modified along with each migration to reflect the state of the table
40
+ # # after the migration is applied.
41
+ # set_schema do
42
+ # primary_key :id
43
+ # String :name
44
+ # String :contact
45
+ # timestamp :created_at, :null => false
46
+ # timestamp :updated_at
47
+ #
48
+ # add_index :name
49
+ # end
50
+ #
51
+ # # Similar to Sequel's TimeStampMigrator, inline migrations have a symbolic
52
+ # # name, which is how they're tracked in the migrations table, and how
53
+ # # they're ordered when they're applied. The second argument is a human-readable
54
+ # # description that can be used for automated change control descriptions or
55
+ # # other tooling.
56
+ # migration( '20110228_1115_add_timestamps', "Add timestamp fields" ) do
57
+ # change do
58
+ # alter_table do
59
+ # add_column :created_at, :timestamp, :null => false
60
+ # add_column :updated_at, :timestamp
61
+ # end
62
+ # update( :created_at => :now[] )
63
+ # end
64
+ # end
65
+ #
66
+ # migration( '20110303_1751_index_name', "Add an index to the name field" ) do
67
+ # change do
68
+ # alter_table do
69
+ # add_index :name
70
+ # end
71
+ # end
72
+ # end
73
+ #
74
+ # end
75
+ # ```
76
+ #
77
+ # Apply pending migrations.
78
+ #
79
+ # ```
80
+ # # bin/migrate
81
+ #
82
+ # require 'acme/model'
83
+ # require 'acme/vendor'
84
+ # # ...
85
+ #
86
+ # puts "Creating new tables, applying any pending migrations..."
87
+ # Acme::Model.migrate
88
+ # ```
89
+ #
90
+ # ## Notable Model Methods
91
+ #
92
+ # See Sequel::Plugins::InlineSchema::ClassMethods for documentation for the methods the
93
+ # plugin adds to your model class/es.
94
+ #
95
+ # * `migration` -- define a migration
96
+ # * `migrate` -- create any missing tables for the receiving model and any subclasses,
97
+ # then run any unapplied migrations.
98
+ #
99
+ # Inline migrations also have model hook methods:
100
+ #
101
+ # * `before_migration`
102
+ # * `after_migration`
103
+ #
104
+ # There's also a method that will return a configured Sequel::Plugins::InlineMigrations::Migrator
105
+ # in case you want to inspect what will happen when you call #migrate:
106
+ #
107
+ # * `migrator`
108
+ #
109
+ module Sequel::Plugins::InlineMigrations
110
+
111
+ ### Sequel plugin API -- Called the first time the plugin is loaded for
112
+ ### this model (unless it was already loaded by an ancestor class),
113
+ ### before including/extending any modules, with the arguments and block
114
+ ### provided to the call to Model.plugin.
115
+ def self::apply( model, *args ) # :nodoc:
116
+ @plugins ||= []
117
+ model.plugin( :subclasses ) # track subclasses
118
+ model.plugin( :inline_schema )
119
+ model.instance_variable_set( :@migrations, {} )
120
+ end
121
+
122
+
123
+ ### A mixin that gets applied to inline migrations to add introspection attributes
124
+ ### and accessors.
125
+ module MigrationIntrospection
126
+
127
+ ### Extension callback -- adds 'name', 'model_class', and 'description' instance
128
+ ### variables.
129
+ def self::extend_object( obj )
130
+ super
131
+ obj.instance_variable_set( :@description, nil )
132
+ obj.instance_variable_set( :@model_class, nil )
133
+ obj.instance_variable_set( :@name, nil )
134
+ end
135
+
136
+ attr_accessor :name, :model_class, :description
137
+
138
+ end # module MigrationIntrospection
139
+
140
+
141
+ # Methods to extend Model classes with.
142
+ #
143
+ # :markup: RDoc
144
+ module ClassMethods
145
+
146
+ # A Regexp for matching valid migration names
147
+ MIGRATION_NAME_PATTERN = /\A\d{8}_\d{4}_\w+\z/
148
+
149
+
150
+ # The Hash of Sequel::SimpleMigration objects for this model, keyed by name
151
+ attr_reader :migrations
152
+
153
+
154
+ ### Add a migration with the specified +name+ and optional +description+. See the
155
+ ### docs for Sequel::Migration for usage, and Sequel::MigrationDSL for the allowed
156
+ ### syntax in the +block+. The name of the migration should be in the form:
157
+ ### <year><month><day>_<hour><minute>_<underbarred_desc>
158
+ def migration( name, description=nil, &block )
159
+ raise ScriptError, "invalid migration name %p" % [ name ] unless
160
+ MIGRATION_NAME_PATTERN.match( name )
161
+
162
+ @migrations ||= {}
163
+ migration_obj = Sequel::MigrationDSL.create( &block )
164
+ migration_obj.extend( MigrationIntrospection )
165
+ migration_obj.name = name
166
+ migration_obj.model_class = self
167
+ migration_obj.description = description
168
+
169
+ @migrations[ name ] = migration_obj
170
+ end
171
+
172
+
173
+ ### Table-migration hook; called once before missing tables are installed and pending
174
+ ### migrations are run.
175
+ def before_migration
176
+ return true
177
+ end
178
+
179
+
180
+ ### Table-migration hook; called once after missing tables are installed and
181
+ ### pending migrations are run.
182
+ def after_migration
183
+ return true
184
+ end
185
+
186
+
187
+ ### After table creation hook to register any existing migrations as being
188
+ ### already applied, as the schema declared by set_schema should be the *latest*
189
+ ### schema, and already incorporate the changes described by the migrations.
190
+ def after_create_table
191
+ super
192
+ self.register_existing_migrations
193
+ end
194
+
195
+
196
+ ### Register any migrations on the receiver as having already been run (as when creating
197
+ ### the table initially).
198
+ def register_existing_migrations
199
+ # Register existing migrations as already being applied
200
+ if self.migrations && !self.migrations.empty?
201
+ migrator = self.migrator
202
+
203
+ self.migrations.each_value do |migration|
204
+ migration_data = {
205
+ name: migration.name,
206
+ model_class: migration.model_class.name
207
+ }
208
+ next unless migrator.dataset.filter( migration_data ).empty?
209
+ self.db.log_info " fast-forwarding migration #{migration.name}..."
210
+ migrator.dataset.insert( migration_data )
211
+ end
212
+ end
213
+ end
214
+
215
+ ### Create any new tables and run any pending migrations. If the optional +target+ is
216
+ ### supplied, the migrations up to (and including) that one will be applied. If
217
+ ### it has already been applied, any from the currently-applied one to it
218
+ ### (inclusive) will be reversed. A target of +nil+ is equivalent to the last one
219
+ def migrate( target=nil )
220
+ migrator = self.migrator( target )
221
+ classes_to_install = self.uninstalled_tables
222
+ self.db.log_info "Classes with tables that need to be installed: %p" % [ classes_to_install ]
223
+
224
+ self.db.transaction do
225
+ self.before_migration
226
+ self.db.log_info "Creating tables that don't yet exist..."
227
+ classes_to_install.each( &:create_table )
228
+
229
+ self.db.log_info "Running any pending migrations..."
230
+ migrator.run
231
+ self.after_migration
232
+ end
233
+ end
234
+
235
+
236
+ ### Return a configured inline migrator set with the given +target+ migration.
237
+ def migrator( target=nil )
238
+ self.db.log_info "Creating the migrator..."
239
+ Sequel::Plugins::InlineMigrations::Migrator.new( self, nil, target: target )
240
+ end
241
+
242
+
243
+ end # module ClassMethods
244
+
245
+
246
+ # Subclass of Sequel::Migrator that provides the logic for extracting and running
247
+ # migrations from the model classes themselves.
248
+ class Migrator < Sequel::Migrator
249
+
250
+ # Default options for .run and #initialize.
251
+ DEFAULT_OPTS = {
252
+ :table => :schema_migrations,
253
+ :column => :name,
254
+ }
255
+
256
+
257
+ ### Migrates the supplied +db+ (a Sequel::Database) using the migrations declared in the
258
+ ### given +baseclass+. The +baseclass+ is the class to gather migrations from; it and all
259
+ ### of its concrete descendents will be considered.
260
+ ###
261
+ ### The +options+ this method understands:
262
+ ###
263
+ ### column
264
+ ### : The column in the table that stores the migration version. Defaults to
265
+ ### `:version`.
266
+ ###
267
+ ### current
268
+ ### : The current version of the database. If not given, it is retrieved from the
269
+ ### database using the `:table` and `:column` options.
270
+ ###
271
+ ### table
272
+ ### : The name of the migrations table. Defaults to `:schema_migrations`.
273
+ ###
274
+ ### target
275
+ ### : The target version to migrate to. If not given, migrates to the
276
+ ### maximum version.
277
+ ###
278
+ ### Examples
279
+ ###
280
+ ### ```
281
+ ### # Assuming Acme::Model is a Sequel::Model subclass, and Acme::Vendor is a subclass
282
+ ### # of that...
283
+ ### Sequel::InlineMigrations::Migrator.run( Acme::Model )
284
+ ### Sequel::InlineMigrations::Migrator.run( Acme::Model, :target => 15, :current => 10 )
285
+ ### Sequel::InlineMigrations::Migrator.run( Acme::Vendor, :column => :app2_version)
286
+ ### Sequel::InlineMigrations::Migrator.run( Acme::Vendor, :column => :app2_version,
287
+ ### :table => :schema_info2 )
288
+ ### ```
289
+ def self::run( baseclass, db=nil, opts={} )
290
+ if db.is_a?( Hash )
291
+ opts = db
292
+ db = nil
293
+ end
294
+
295
+ new( baseclass, db, opts ).run
296
+ end
297
+
298
+
299
+ ### Create a new Migrator that will organize migrations defined for
300
+ ### +baseclass+ or any of its subclasses for the specified +db+.
301
+ ### See Sequel::Plugins::InlineMigrations::Migrator.run for argument details.
302
+ def initialize( baseclass, db=nil, opts={} )
303
+ if db.is_a?( Hash )
304
+ opts = db
305
+ db = nil
306
+ end
307
+
308
+ db ||= baseclass.db
309
+
310
+ opts = DEFAULT_OPTS.merge( opts )
311
+ schema, table = db.send( :schema_and_table, opts[:table] )
312
+
313
+ @db = db
314
+ @baseclass = baseclass
315
+ @table = opts[ :table ]
316
+ @column = opts[ :column ]
317
+ @target = opts[ :target ]
318
+ @dataset = make_schema_dataset( @db, @table, @column )
319
+ end
320
+
321
+
322
+ ######
323
+ public
324
+ ######
325
+
326
+ # The database to which the migrator will apply its migrations; a Sequel::Database.
327
+ attr_reader :db
328
+
329
+ # The Class at the top of the hierarchy from which migrations will be fetched
330
+ attr_reader :baseclass
331
+
332
+ # The name of the migration table as a Sequel::SQL::QualifiedIdentifier.
333
+ attr_reader :table
334
+
335
+ # The name of the column which will contain the names of applied migrations as a Symbol.
336
+ attr_reader :column
337
+
338
+ # The migration table dataset (a Sequel::Dataset).
339
+ attr_reader :dataset
340
+
341
+ # The name of the target migration to play up or down to as a String.
342
+ attr_reader :target
343
+
344
+
345
+ ### Apply all migrations to the database
346
+ def run
347
+ applied, pending = self.get_partitioned_migrations
348
+
349
+ # If no target was specified, and there are no pending
350
+ # migrations, return early.
351
+ return if pending.empty? && self.target.nil?
352
+
353
+ # If no target was specified, the last one is the target
354
+ target = self.target || pending.last.name
355
+ migrations = nil
356
+ direction = nil
357
+
358
+ if target == '0'
359
+ direction = :down
360
+ migrations = applied.reverse
361
+
362
+ elsif tgtidx = pending.find_index {|m| m.name == target }
363
+ migrations = pending[ 0..tgtidx ]
364
+ direction = :up
365
+
366
+ elsif tgtidx = applied.find_index {|m| m.name == target }
367
+ migrations = applied[ tgtidx..-1 ].reverse
368
+ direction = :down
369
+
370
+ else
371
+ raise Sequel::Error, "couldn't find migration %p"
372
+ end
373
+
374
+ # Run the selected migrations
375
+ self.db.log_info "Migrating %d steps %s..." % [ migrations.length, direction ]
376
+ migrations.each do |migration|
377
+ start = Time.now
378
+ self.db.log_info "Begin: %s, direction: %s" %
379
+ [ migration.description, direction ]
380
+
381
+ self.db.transaction do
382
+ migration.apply( self.db, direction )
383
+
384
+ mclass = migration.model_class.name
385
+ if direction == :up
386
+ self.dataset.insert( self.column => migration.name, :model_class => mclass )
387
+ else
388
+ self.dataset.filter( self.column => migration.name, :model_class => mclass ).delete
389
+ end
390
+ end
391
+
392
+ self.db.log_info " finished: %s, direction: %s (%0.6fs)" %
393
+ [ migration.description, direction, Time.now - start ]
394
+ end
395
+ end
396
+
397
+
398
+ ### Fetch an Array of all model classes which are descended from the migrating subclass,
399
+ ### inclusive.
400
+ def all_migrating_model_classes
401
+ return [ self.baseclass ] + self.baseclass.descendents
402
+ end
403
+
404
+
405
+ ### Returns any migration objects found in the migrating subclass or any of its
406
+ ### descendents as an Array of Sequel::SimpleMigration objects, sorted by the migration
407
+ ### name and the name of its migrating class.
408
+ def all_migrations
409
+ migrations = self.all_migrating_model_classes.
410
+ collect( &:migrations ).
411
+ compact.
412
+ inject {|all, hash| all.merge(hash) }
413
+
414
+ return migrations.values.sort_by {|m| [m.name, m.model_class.name] }
415
+ end
416
+
417
+
418
+ ### Returns two Arrays of migrations, the first one containing those which have already
419
+ ### been applied, and the second containing migrations which are pending. Migrations that
420
+ ### have been marked as applied but are (no longer) defined by a model class will be
421
+ ### ignored.
422
+ def get_partitioned_migrations
423
+
424
+ # Get the list of applied migrations for the subclass and its descendents.
425
+ migrating_class_names = self.all_migrating_model_classes.map( &:name ).compact
426
+ applied_map = self.dataset.
427
+ filter( :model_class => migrating_class_names ).
428
+ select_hash( column, :model_class )
429
+
430
+ # Split up the migrations by whether or not it exists in the map of applied migrations.
431
+ # Each one is removed from the map, so it can be checked for consistency
432
+ part_migrations = self.all_migrations.partition do |migration|
433
+ applied_map.delete( migration.name )
434
+ end
435
+
436
+ # If there are any "applied" migrations left, it's likely been deleted since it was
437
+ # applied, so just ignore it.
438
+ unless applied_map.empty?
439
+ applied_map.each do |migration, classname|
440
+ db.log_info "No %s migration defined in %s; ignoring it." %
441
+ [ migration, classname ]
442
+ end
443
+ end
444
+
445
+ return part_migrations
446
+ end
447
+
448
+
449
+ #######
450
+ private
451
+ #######
452
+
453
+ ### Returns the dataset for the schema_migrations table. If no such table
454
+ ### exists, it is automatically created.
455
+ def make_schema_dataset( db, table, column )
456
+ ds = db.from( table )
457
+ db.log_info "Schema dataset is: %p" % [ ds ]
458
+
459
+ if !db.table_exists?( table ) || ds.columns.empty?
460
+ db.log_info "No migrations table: Installing one."
461
+ db.create_table( table ) do
462
+ String column, :primary_key => true
463
+ String :model_class, :null => false
464
+ end
465
+ elsif !ds.columns.include?( column )
466
+ raise Sequel::Error, "Migrator table %p does not contain column %p (%p)" %
467
+ [ table, column, ds.columns ]
468
+ end
469
+
470
+ return ds
471
+ end
472
+
473
+ end # class Migrator
474
+
475
+ end # Sequel::Plugins::InlineMigrations