sequel-inline_schema 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0586820d7a0cb25e530b431dee73a22dc205296a1cf15a945602f554ba9b3608'
4
- data.tar.gz: 4646d1e2a538da9554864bb15db471b2eb9a2c6e920b8bdcbb462883d1724517
3
+ metadata.gz: ba38d991fa938c8b49fd8d6b395609ed80191ea733dcbfc91adc539b23a1ad2c
4
+ data.tar.gz: 58cac869e2b380e53b47e17ba0aa8f8bb47e5553cf5a9ac57b499bd962d76c4e
5
5
  SHA512:
6
- metadata.gz: 06013b428fbd164f5717a815391d65723033648ef301621b7d551ebcfb8e18c6c0c64a31678a0e36885f89ee3d7e3f3e717fbeab9722f2537dbfb9e26d16c37d
7
- data.tar.gz: 92cf82542922e762fc32971cce5b16cdeebc6922940a019bc4b2e80ac8e01ae40a6cea8d2863d3da2fbd71761ee9022b5587fa6b3ac471a62a57cddf3f31129e
6
+ metadata.gz: cc518f2f4548126ce2e79c7c5fa619b4d2be33e0db757b6cd90a05bb8b5804938819352225c0ce60b9871e1b526e0d90dff743ca3304215ce33f3eef98c9ae88
7
+ data.tar.gz: f67ec3eca4fb4e65f1e66b1494d61e0c5f3b636074b88becfb3f28a70e107f3be44b67b613a50dbf63def6778b70f0befd5eb39b043b96ddc34f760ca2d16c73
Binary file
data.tar.gz.sig CHANGED
Binary file
data/History.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ---
4
4
 
5
+ ## v0.3.0 [2020-02-10] Michael Granger <ged@faeriemud.org>
6
+
7
+ Improvements:
8
+
9
+ - Add inline view declaration
10
+
11
+
5
12
  ## v0.2.0 [2020-01-18] Michael Granger <ged@faeriemud.org>
6
13
 
7
14
  Improvements:
data/Rakefile CHANGED
@@ -4,6 +4,6 @@ require 'rake/deveiate'
4
4
 
5
5
  Rake::DevEiate.setup( 'sequel-inline_schema' ) do |project|
6
6
  project.publish_to = 'deveiate:/usr/local/www/public/code'
7
- project.required_ruby_version = '~> 2.4'
7
+ project.required_ruby_version = '~> 2.5'
8
8
  end
9
9
 
@@ -7,7 +7,7 @@ require 'sequel/plugins/inline_schema'
7
7
  module Sequel::InlineSchema
8
8
 
9
9
  # Package version
10
- VERSION = '0.2.0'
10
+ VERSION = '0.3.0'
11
11
 
12
12
  # Version control revision
13
13
  REVISION = %q$Revision$
@@ -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 if it doesn't exist.
185
+ ### Drops table if it already exists, do nothing.
149
186
  def drop_table?
150
- self.db.drop_table?( self.table_name )
187
+ self.drop_table if self.table_exists?
151
188
  end
152
189
 
153
190
 
154
- ### Called before the table is dropped.
155
- def before_drop_table
156
- # No-op
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
- ### Called after the table is dropped.
161
- def after_drop_table
162
- # No-op
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
- ### Returns the table schema created with set_schema.
167
- def schema
168
- if !@schema && @schema_block
169
- self.set_dataset( self.db[@schema_name] ) if @schema_name
170
- @schema = self.db.create_table_generator( &@schema_block )
171
- self.set_primary_key( @schema.primary_key_name ) if @schema.primary_key_name
172
- end
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
- ### Defines a table schema (see Schema::CreateTableGenerator for more information).
178
- ###
179
- ### This will also set the dataset if you provide a +name+, as well as setting
180
- ### the primary key if you define one in the passed block.
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
- ### Returns true if table exists, false otherwise.
195
- def table_exists?
196
- return self.db.table_exists?( self.table_name )
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
- ### Return an Array of model table names that don't yet exist, in the order they
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.2.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-01-19 00:00:00.000000000 Z
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.4'
143
+ version: '2.5'
144
144
  required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  requirements:
146
146
  - - ">="
metadata.gz.sig CHANGED
Binary file