sequel-inline_schema 0.0.1

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