sequel-inline_schema 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/History.md +7 -0
- data/Rakefile +1 -1
- data/lib/sequel/inline_schema.rb +1 -1
- data/lib/sequel/plugins/inline_migrations.rb +7 -0
- data/lib/sequel/plugins/inline_schema.rb +175 -48
- data/spec/sequel/plugins/inline_schema_spec.rb +44 -2
- metadata +3 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba38d991fa938c8b49fd8d6b395609ed80191ea733dcbfc91adc539b23a1ad2c
|
4
|
+
data.tar.gz: 58cac869e2b380e53b47e17ba0aa8f8bb47e5553cf5a9ac57b499bd962d76c4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc518f2f4548126ce2e79c7c5fa619b4d2be33e0db757b6cd90a05bb8b5804938819352225c0ce60b9871e1b526e0d90dff743ca3304215ce33f3eef98c9ae88
|
7
|
+
data.tar.gz: f67ec3eca4fb4e65f1e66b1494d61e0c5f3b636074b88becfb3f28a70e107f3be44b67b613a50dbf63def6778b70f0befd5eb39b043b96ddc34f760ca2d16c73
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/History.md
CHANGED
data/Rakefile
CHANGED
data/lib/sequel/inline_schema.rb
CHANGED
@@ -212,6 +212,7 @@ module Sequel::Plugins::InlineMigrations
|
|
212
212
|
end
|
213
213
|
end
|
214
214
|
|
215
|
+
|
215
216
|
### Create any new tables and run any pending migrations. If the optional +target+ is
|
216
217
|
### supplied, the migrations up to (and including) that one will be applied. If
|
217
218
|
### it has already been applied, any from the currently-applied one to it
|
@@ -220,6 +221,8 @@ module Sequel::Plugins::InlineMigrations
|
|
220
221
|
migrator = self.migrator( target )
|
221
222
|
classes_to_install = self.uninstalled_tables
|
222
223
|
self.db.log_info "Classes with tables that need to be installed: %p" % [ classes_to_install ]
|
224
|
+
views_to_install = self.uninstalled_views
|
225
|
+
self.db.log_info "Views to install: %p" % [ views_to_install.map(&:table_name) ]
|
223
226
|
|
224
227
|
self.db.transaction do
|
225
228
|
self.before_migration
|
@@ -229,6 +232,9 @@ module Sequel::Plugins::InlineMigrations
|
|
229
232
|
self.db.log_info "Running any pending migrations..."
|
230
233
|
migrator.run
|
231
234
|
self.after_migration
|
235
|
+
|
236
|
+
self.db.log_info "(Re)-creating any modeled views..."
|
237
|
+
views_to_install.each( &:create_view )
|
232
238
|
end
|
233
239
|
end
|
234
240
|
|
@@ -477,4 +483,5 @@ module Sequel::Plugins::InlineMigrations
|
|
477
483
|
|
478
484
|
end # class Migrator
|
479
485
|
|
486
|
+
|
480
487
|
end # Sequel::Plugins::InlineMigrations
|
@@ -100,6 +100,55 @@ module Sequel::Plugins::InlineSchema
|
|
100
100
|
# Sequel plugin API -- add these methods to model classes which load the plugin.
|
101
101
|
module ClassMethods
|
102
102
|
|
103
|
+
### Extension callback -- add some class instance variables to keep track of
|
104
|
+
### schema info.
|
105
|
+
def self::extended( model_class )
|
106
|
+
super
|
107
|
+
|
108
|
+
model_class.require_valid_table = false
|
109
|
+
|
110
|
+
# The Sequel::Dataset used to create the model's view (if it's modelling a view
|
111
|
+
# instead of a table). Setting this causes the model's schema to be ignored when
|
112
|
+
# its table is created, creating a view by the same name instead.
|
113
|
+
model_class.singleton_class.attr_accessor( :view_dataset )
|
114
|
+
|
115
|
+
# The options used when creating the view for the model.
|
116
|
+
model_class.singleton_class.attr_accessor( :view_options )
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
### Returns the table schema created with set_schema.
|
121
|
+
def schema
|
122
|
+
if !@schema && @schema_block
|
123
|
+
self.set_dataset( self.db[@schema_name] ) if @schema_name
|
124
|
+
@schema = self.db.create_table_generator( &@schema_block )
|
125
|
+
self.set_primary_key( @schema.primary_key_name ) if @schema.primary_key_name
|
126
|
+
end
|
127
|
+
return @schema || ( superclass.schema unless superclass == Sequel::Model )
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
### Defines a table schema (see Schema::CreateTableGenerator for more information).
|
132
|
+
###
|
133
|
+
### This will also set the dataset if you provide a +name+, as well as setting
|
134
|
+
### the primary key if you define one in the passed block.
|
135
|
+
###
|
136
|
+
### Since this plugin allows you to declare the schema inline with the model
|
137
|
+
### class that acts as its interface, the table will not always exist when the
|
138
|
+
### class loads, so calling #set_schema will call require_valid_table to `false`
|
139
|
+
### for you. You can disable this by passing `require_table: true`.
|
140
|
+
def set_schema( name=nil, require_table: false, &block )
|
141
|
+
self.require_valid_table = require_table
|
142
|
+
@schema = nil
|
143
|
+
@schema_name = name
|
144
|
+
@schema_block = block
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
#
|
149
|
+
# Table utilities
|
150
|
+
#
|
151
|
+
|
103
152
|
### Creates table, using the column information from set_schema.
|
104
153
|
def create_table( *args, &block )
|
105
154
|
self.set_schema( *args, &block ) if block
|
@@ -125,18 +174,6 @@ module Sequel::Plugins::InlineSchema
|
|
125
174
|
end
|
126
175
|
|
127
176
|
|
128
|
-
### Called before the table is created.
|
129
|
-
def before_create_table
|
130
|
-
# No-op
|
131
|
-
end
|
132
|
-
|
133
|
-
|
134
|
-
### Called after the table is created.
|
135
|
-
def after_create_table
|
136
|
-
# No-op
|
137
|
-
end
|
138
|
-
|
139
|
-
|
140
177
|
### Drops table. If the table doesn't exist, this will probably raise an error.
|
141
178
|
def drop_table
|
142
179
|
self.before_drop_table
|
@@ -145,58 +182,96 @@ module Sequel::Plugins::InlineSchema
|
|
145
182
|
end
|
146
183
|
|
147
184
|
|
148
|
-
### Drops table if it already exists, do nothing
|
185
|
+
### Drops table if it already exists, do nothing.
|
149
186
|
def drop_table?
|
150
|
-
self.
|
187
|
+
self.drop_table if self.table_exists?
|
151
188
|
end
|
152
189
|
|
153
190
|
|
154
|
-
###
|
155
|
-
def
|
156
|
-
|
191
|
+
### Returns true if table exists, false otherwise.
|
192
|
+
def table_exists?
|
193
|
+
return self.db.table_exists?( self.table_name )
|
157
194
|
end
|
158
195
|
|
159
196
|
|
160
|
-
###
|
161
|
-
|
162
|
-
|
197
|
+
### Set the dataset to use for the model to +ds+. If a +block+ is provided, it will be
|
198
|
+
### called with the specified +ds+, and should return the modified dataset to use. Any
|
199
|
+
### +options+ that are given will be passed to Sequel::Database#create_or_replace_view
|
200
|
+
def set_view_dataset( ds=nil, **options ) # :yield: ds
|
201
|
+
ds = yield( ds ) if block_given?
|
202
|
+
|
203
|
+
self.view_dataset = ds
|
204
|
+
self.view_options = options
|
163
205
|
end
|
164
206
|
|
165
207
|
|
166
|
-
###
|
167
|
-
def
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
return @schema || ( superclass.schema unless superclass == Sequel::Model )
|
208
|
+
### Create the view for this model class.
|
209
|
+
def create_view( options={} )
|
210
|
+
dataset = self.view_dataset or raise "No view declared for this model."
|
211
|
+
options = self.view_options.merge( options )
|
212
|
+
|
213
|
+
self.db.log_info "Creating view %s(%p): %s" % [ self.table_name, options, dataset.sql ]
|
214
|
+
self.db.create_or_replace_view( self.table_name, dataset, options )
|
174
215
|
end
|
175
216
|
|
176
217
|
|
177
|
-
###
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
###
|
182
|
-
### Since this plugin allows you to declare the schema inline with the model
|
183
|
-
### class that acts as its interface, the table will not always exist when the
|
184
|
-
### class loads, so calling #set_schema will call require_valid_table to `false`
|
185
|
-
### for you. You can disable this by passing `require_table: true`.
|
186
|
-
def set_schema( name=nil, require_table: false, &block )
|
187
|
-
self.require_valid_table = require_table
|
188
|
-
@schema = nil
|
189
|
-
@schema_name = name
|
190
|
-
@schema_block = block
|
218
|
+
### Drops the view if it exists and then runs #create_view.
|
219
|
+
def create_view!( options={} )
|
220
|
+
self.drop_view?
|
221
|
+
return self.create_view
|
191
222
|
end
|
192
223
|
|
193
224
|
|
194
|
-
###
|
195
|
-
def
|
196
|
-
|
225
|
+
### Creates the view unless it already exists.
|
226
|
+
def create_view?( options={} )
|
227
|
+
self.create_view( options ) unless self.view_exists?
|
197
228
|
end
|
198
229
|
|
199
230
|
|
231
|
+
### Refresh the view for this model class. This can only
|
232
|
+
### be called on materialized views.
|
233
|
+
def refresh_view
|
234
|
+
self.db.refresh_view( self.table_name )
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
### Drop the view backing this model.
|
239
|
+
def drop_view
|
240
|
+
self.before_drop_view
|
241
|
+
self.db.drop_view( self.table_name )
|
242
|
+
self.after_drop_view
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
### Drop the view if it already exists, otherwise do nothing.
|
247
|
+
def drop_view?
|
248
|
+
self.drop_view if self.view_exists?
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
### Returns true if the view associated with this model exists, false otherwise.
|
253
|
+
def view_exists?
|
254
|
+
# Make shortcuts for fully-qualified names
|
255
|
+
class_table = Sequel[:pg_catalog][:pg_class].as( :c )
|
256
|
+
ns_table = Sequel[:pg_catalog][:pg_namespace].as( :n )
|
257
|
+
is_visible = Sequel[:pg_catalog][:pg_table_is_visible]
|
258
|
+
|
259
|
+
ds = db[ class_table ].
|
260
|
+
join( ns_table, oid: :relnamespace )
|
261
|
+
ds = ds.where( Sequel[:c][:relkind] => ['v', 'm'] ).
|
262
|
+
exclude( Sequel[:n][:nspname] => /^pg_toast/ ).
|
263
|
+
where( Sequel[:c][:relname] => self.table_name ).
|
264
|
+
where( Sequel.function(is_visible, Sequel[:c][:oid]) )
|
265
|
+
|
266
|
+
return ds.count == 1
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
|
271
|
+
#
|
272
|
+
# Hooks
|
273
|
+
#
|
274
|
+
|
200
275
|
### Table-creation hook; called on a model class before its table is created.
|
201
276
|
def before_create_table
|
202
277
|
return true
|
@@ -209,18 +284,71 @@ module Sequel::Plugins::InlineSchema
|
|
209
284
|
end
|
210
285
|
|
211
286
|
|
212
|
-
###
|
287
|
+
### View-creation hook; called before the backing view is created.
|
288
|
+
def before_create_view
|
289
|
+
return true
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
### View-creation hook; called after the backing view is created.
|
294
|
+
def after_create_view
|
295
|
+
return true
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
### Table-drop hook; called before the table is dropped.
|
300
|
+
def before_drop_table
|
301
|
+
return true
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
### Table-drop hook; called after the table is dropped.
|
306
|
+
def after_drop_table
|
307
|
+
return true
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
### View-creation hook; called before the backing view is created.
|
312
|
+
def before_create_view
|
313
|
+
return true
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
### View-creation hook; called after the backing view is created.
|
318
|
+
def after_create_view
|
319
|
+
return true
|
320
|
+
end
|
321
|
+
|
322
|
+
|
323
|
+
#
|
324
|
+
# Schema-state introspection
|
325
|
+
#
|
326
|
+
|
327
|
+
### Return an Array of model classes whose tables don't yet exist, in the order they
|
213
328
|
### need to be created to satisfy foreign key constraints.
|
214
329
|
def uninstalled_tables
|
215
330
|
self.db.log_info " searching for unbacked model classes..."
|
216
331
|
|
217
332
|
self.tsort.find_all do |modelclass|
|
218
|
-
next unless modelclass.name && modelclass.name != ''
|
333
|
+
next unless modelclass.name && modelclass.name != '' && !modelclass.is_view_class?
|
219
334
|
!modelclass.table_exists?
|
220
335
|
end.uniq( &:table_name )
|
221
336
|
end
|
222
337
|
|
223
338
|
|
339
|
+
### Return an Array of model classes whose views don't yet exist, in the order
|
340
|
+
### they need to be created.
|
341
|
+
def uninstalled_views
|
342
|
+
return self.tsort.find_all( &:is_view_class? ).reject( &:table_exists? )
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
### Returns +true+ if the receiver is defined via a view rather than a table.
|
347
|
+
def is_view_class?
|
348
|
+
return self.respond_to?( :view_dataset ) && self.view_dataset ? true : false
|
349
|
+
end
|
350
|
+
|
351
|
+
|
224
352
|
#########
|
225
353
|
protected
|
226
354
|
#########
|
@@ -275,7 +403,6 @@ module Sequel::Plugins::InlineSchema
|
|
275
403
|
end
|
276
404
|
end
|
277
405
|
|
278
|
-
|
279
406
|
end # module ClassMethods
|
280
407
|
|
281
408
|
end # module Sequel::Plugin::Schema
|
@@ -22,6 +22,11 @@ describe Sequel::Plugins::InlineSchema do
|
|
22
22
|
name.to_sym
|
23
23
|
end
|
24
24
|
|
25
|
+
let( :view ) do
|
26
|
+
name = "test_view_%s" % [ SecureRandom.hex(8) ]
|
27
|
+
name.to_sym
|
28
|
+
end
|
29
|
+
|
25
30
|
let( :model_class ) do
|
26
31
|
mclass = Class.new( Sequel::Model )
|
27
32
|
mclass.set_dataset( db[table] )
|
@@ -29,6 +34,8 @@ describe Sequel::Plugins::InlineSchema do
|
|
29
34
|
mclass
|
30
35
|
end
|
31
36
|
|
37
|
+
let ( :view_dataset ) { model_class.dataset.group_and_count( :age ) }
|
38
|
+
|
32
39
|
let( :valid_pg_attributes ) do
|
33
40
|
[
|
34
41
|
{
|
@@ -85,7 +92,7 @@ describe Sequel::Plugins::InlineSchema do
|
|
85
92
|
when /CREATE TABLE/
|
86
93
|
created = true
|
87
94
|
else
|
88
|
-
fail "Unhandled query"
|
95
|
+
fail "Unhandled query %p" % [ query ]
|
89
96
|
end
|
90
97
|
end
|
91
98
|
end
|
@@ -130,7 +137,6 @@ describe Sequel::Plugins::InlineSchema do
|
|
130
137
|
model_class.create_table!
|
131
138
|
|
132
139
|
expect( db.sqls ).to include(
|
133
|
-
%{DROP TABLE IF EXISTS "#{table}"},
|
134
140
|
%{CREATE TABLE "#{table}" ("id" serial PRIMARY KEY, "name" text, "age" integer)}
|
135
141
|
)
|
136
142
|
end
|
@@ -179,6 +185,42 @@ describe Sequel::Plugins::InlineSchema do
|
|
179
185
|
end
|
180
186
|
|
181
187
|
|
188
|
+
|
189
|
+
it "allows a model to create a view instead of a table" do
|
190
|
+
view_class = Class.new( Sequel::Model )
|
191
|
+
view_class.plugin( :inline_schema )
|
192
|
+
view_class.set_dataset( db[view] )
|
193
|
+
view_class.set_view_dataset { view_dataset.naked }
|
194
|
+
|
195
|
+
db.fetch = fake_db_fetcher
|
196
|
+
db.sqls.clear
|
197
|
+
|
198
|
+
view_class.create_view
|
199
|
+
|
200
|
+
expect( db.sqls ).to include(
|
201
|
+
%{CREATE OR REPLACE VIEW "#{view}" AS SELECT "age", count(*) AS "count" FROM "#{table}" GROUP BY "age"}
|
202
|
+
)
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
it "allows a model to craete a materialized view instead of a table" do
|
207
|
+
materialized_view_class = Class.new( Sequel::Model )
|
208
|
+
materialized_view_class.plugin( :inline_schema )
|
209
|
+
materialized_view_class.set_dataset( db[view] )
|
210
|
+
materialized_view_class.set_view_dataset( materialized: true ) { view_dataset.naked }
|
211
|
+
|
212
|
+
db.fetch = fake_db_fetcher
|
213
|
+
db.sqls.clear
|
214
|
+
|
215
|
+
materialized_view_class.create_view
|
216
|
+
|
217
|
+
expect( db.sqls ).to include(
|
218
|
+
%{CREATE OR REPLACE MATERIALIZED VIEW "#{view}" AS SELECT "age", count(*) AS "count" } +
|
219
|
+
%{FROM "#{table}" GROUP BY "age"}
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
|
182
224
|
describe "table-creation ordering" do
|
183
225
|
|
184
226
|
let( :fake_db_fetcher ) do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel-inline_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Granger
|
@@ -34,7 +34,7 @@ cert_chain:
|
|
34
34
|
jBZSA+N+xUTgUWpXjjwsLZjzJkhWATJWq+krNXcqpwXo6HsjmdUxoFMt63RBb+sI
|
35
35
|
XrxOxp8o0uOkU7FdLSGsyqJ2LzsR4obN
|
36
36
|
-----END CERTIFICATE-----
|
37
|
-
date: 2020-
|
37
|
+
date: 2020-02-10 00:00:00.000000000 Z
|
38
38
|
dependencies:
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
40
|
name: sequel
|
@@ -140,7 +140,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
140
|
requirements:
|
141
141
|
- - "~>"
|
142
142
|
- !ruby/object:Gem::Version
|
143
|
-
version: '2.
|
143
|
+
version: '2.5'
|
144
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
145
|
requirements:
|
146
146
|
- - ">="
|
metadata.gz.sig
CHANGED
Binary file
|