story-teller 1.1.3

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +623 -0
  3. data/README.md +188 -0
  4. data/Rakefile +58 -0
  5. data/config/database.yml +37 -0
  6. data/exe/inform.rb +6 -0
  7. data/game/config.yml +5 -0
  8. data/game/example.inf +90 -0
  9. data/game/example.rb +105 -0
  10. data/game/forms/example_form.rb +2 -0
  11. data/game/grammar/admin.inf.rb +185 -0
  12. data/game/grammar/builder.inf.rb +310 -0
  13. data/game/grammar/game_grammar.inf.rb +6 -0
  14. data/game/grammar/meta.inf.rb +41 -0
  15. data/game/languages/english.rb +571 -0
  16. data/game/models/example_model.rb +2 -0
  17. data/game/modules/example_module.rb +9 -0
  18. data/game/modules/parser_extensions.rb +264 -0
  19. data/game/rules/example_state.rb +2 -0
  20. data/game/scripts/example_script.rb +2 -0
  21. data/game/topics/example_topic.rb +2 -0
  22. data/game/verbs/game_verbs.rb +35 -0
  23. data/game/verbs/metaverbs.rb +2066 -0
  24. data/lib/story_teller/application.rb +82 -0
  25. data/lib/story_teller/cli.rb +35 -0
  26. data/lib/story_teller/color.rb +144 -0
  27. data/lib/story_teller/config.rb +61 -0
  28. data/lib/story_teller/curses_adapter.rb +30 -0
  29. data/lib/story_teller/database.rb +527 -0
  30. data/lib/story_teller/game/loader.rb +276 -0
  31. data/lib/story_teller/game.rb +22 -0
  32. data/lib/story_teller/inform/models.rb +42 -0
  33. data/lib/story_teller/inform/relational/link.rb +239 -0
  34. data/lib/story_teller/inform/relational/module.rb +203 -0
  35. data/lib/story_teller/inform/relational/object.rb +546 -0
  36. data/lib/story_teller/inform/relational/tag.rb +152 -0
  37. data/lib/story_teller/options.rb +151 -0
  38. data/lib/story_teller/persistence.rb +340 -0
  39. data/lib/story_teller/player_character.rb +99 -0
  40. data/lib/story_teller/privileges.rb +55 -0
  41. data/lib/story_teller/runtime.rb +381 -0
  42. data/lib/story_teller/snapshots.rb +412 -0
  43. data/lib/story_teller/terminal.rb +58 -0
  44. data/lib/story_teller/version.rb +24 -0
  45. data/lib/story_teller_cli.rb +34 -0
  46. metadata +158 -0
@@ -0,0 +1,546 @@
1
+ # lib/story_teller/inform/relational/object.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: false
4
+
5
+ # Copyright Nels Nelson 2008-2025 but freely usable (see license)
6
+ #
7
+ # This file is part of the Inform Runtime.
8
+ #
9
+ # The Inform Runtime is free software: you can redistribute it and/or
10
+ # modify it under the terms of the GNU General Public License as published
11
+ # by the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # The Inform Runtime is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ require 'sequel'
23
+
24
+ # Object
25
+
26
+ if defined? Sequel::Migration
27
+ # The ObjectSetup class
28
+ class ObjectSetup < Sequel::Migration
29
+ # rubocop: disable Metrics/MethodLength
30
+ def up
31
+ create_table? :object do
32
+ primary_key :id
33
+ foreign_key :parent_id, :object, on_delete: :set_null
34
+ index :name
35
+ index :created_at
36
+
37
+ String :name, text: true
38
+ String :short_name, text: true
39
+ String :description, text: true
40
+ String :object_type, null: false
41
+ String :properties, text: true, default: {}.to_yaml.strip
42
+ DateTime :created_at
43
+ DateTime :modified_at
44
+ end
45
+ end
46
+ # rubocop: enable Metrics/MethodLength
47
+
48
+ def down
49
+ drop_table(:object, cascade: true) if table_exists? :object
50
+ end
51
+ end
52
+ end
53
+
54
+ # if defined? ObjectSetup
55
+ # ObjectSetup.down if ObjectSetup.respond_to?(:down)
56
+ # end
57
+ if defined? ObjectSetup
58
+ ObjectSetup.up if ObjectSetup.respond_to?(:up)
59
+ end
60
+
61
+ def Object(name, klass = Inform::Object, &block)
62
+ obj = if klass.respond_to?(:fetch_or_create_by_name)
63
+ klass.fetch_or_create_by_name(name)
64
+ else
65
+ klass.new(name)
66
+ end
67
+ obj.with(&block) if block_given?
68
+ obj.save_changes if obj.respond_to?(:save_changes)
69
+ obj
70
+ end
71
+
72
+ # The Inform module
73
+ module Inform
74
+ # The Inform::Object class
75
+ # rubocop: disable Metrics/ClassLength
76
+ class Object < Sequel::Model
77
+ logger
78
+ plugin :rcte_tree
79
+ plugin :serialization
80
+ plugin :single_table_inheritance, :object_type
81
+ serialize_attributes :yaml, :properties
82
+
83
+ one_to_many :links, join_table: :link, key: :from_id
84
+ one_to_many :tagged, key: :object_id
85
+ one_to_many :modularized, key: :object_id
86
+ many_to_many :modules,
87
+ class: 'Inform::Module',
88
+ join_table: :modularized,
89
+ left_key: :object_id,
90
+ right_key: :module_id
91
+ many_to_many :tags,
92
+ class: 'Inform::Tag',
93
+ join_table: :tagged,
94
+ left_key: :object_id,
95
+ right_key: :tag_id
96
+ one_to_many :children,
97
+ key: :parent_id,
98
+ class: self,
99
+ order: %i[name id],
100
+ eager: %i[modules tags links]
101
+
102
+ # rubocop: disable Metrics/MethodLength
103
+ def initialize(*args, &block)
104
+ if args.empty?
105
+ super(&nil)
106
+ elsif args.first.is_a?(String)
107
+ short_name = args.first
108
+ super({ name: short_name, short_name: short_name }, &nil)
109
+ else
110
+ super(*args, &nil)
111
+ end
112
+ save
113
+ @tags_semaphore = Mutex.new
114
+ @semaphore = Mutex.new
115
+ self.with(&block) unless block.nil?
116
+ log.debug "Initializing #{self}"
117
+ end
118
+ # rubocop: enable Metrics/MethodLength
119
+
120
+ def self.fetch_or_create_by_name(name)
121
+ key = name.to_s
122
+ object = first(name: key)
123
+ object ||= all.find { |candidate| candidate.name_values.include?(key) }
124
+ object || new(key)
125
+ end
126
+
127
+ def name_values
128
+ Array(self.values[:name]).flatten.map(&:to_s)
129
+ end
130
+
131
+ # def name(*args)
132
+ # return args.first.object_name if args.first.respond_to?(:object_name)
133
+ # return self.values[:name] if args.empty?
134
+
135
+ # self.values[:name] = args.length > 1 ? args.join(' ') : args
136
+ # self.save
137
+ # self.values[:name]
138
+ # end
139
+ def name(*args)
140
+ return args.first.object_name if args.first.respond_to?(:object_name)
141
+ return self.values[:name] if args.empty?
142
+
143
+ names = args.flatten
144
+ self.values[:name] = names
145
+ self.save
146
+ self.values[:name] = names
147
+ end
148
+
149
+ def name=(value)
150
+ name(value)
151
+ end
152
+
153
+ # def name_words
154
+ # self.values[:name].split(/[,\s]+/).join(', ')
155
+ # end
156
+ def name_words
157
+ name_values.join(', ')
158
+ end
159
+
160
+ # rubocop: disable Metrics/MethodLength
161
+ def short_name(*args)
162
+ unless args.empty?
163
+ self.short_name = args.first
164
+ return
165
+ end
166
+
167
+ textual_name = self.values[:short_name]
168
+ synonyms = self.values[:name]
169
+ if textual_name.nil? || (textual_name.respond_to?(:empty?) && textual_name.empty?)
170
+ self.values[:short_name] = synonyms
171
+ self.save
172
+ self.values[:short_name]
173
+ else
174
+ textual_name
175
+ end
176
+ end
177
+ # rubocop: enable Metrics/MethodLength
178
+
179
+ def object_name
180
+ self.values[:display_name] || self.values[:short_name] || self.values[:name]
181
+ end
182
+
183
+ # def description(*args)
184
+ # return self.values[:description] if args.empty?
185
+
186
+ # value = args.length > 1 ? args.join : args.first
187
+ # self.values[:description] = value
188
+ # self.save_changes if self.respond_to?(:save_changes)
189
+ # value
190
+ # end
191
+
192
+ # alias description= description
193
+
194
+ def description(*args)
195
+ return self[:description] if args.empty?
196
+
197
+ value = args.length > 1 ? args.join : args.first
198
+ self[:description] = value
199
+ save_changes if respond_to?(:save_changes)
200
+ value
201
+ end
202
+
203
+ def description=(value)
204
+ self[:description] = value
205
+ save_changes if respond_to?(:save_changes)
206
+ end
207
+
208
+ def semaphore
209
+ @semaphore ||= Mutex.new
210
+ end
211
+
212
+ def tags_semaphore
213
+ @tags_semaphore ||= Mutex.new
214
+ end
215
+
216
+ alias tags_original tags
217
+
218
+ def tags
219
+ tags_semaphore.synchronize { tags_original }
220
+ end
221
+
222
+ def routines
223
+ ((self.methods - Object.instance_methods) - Inform::Object.instance_methods).sort.uniq
224
+ end
225
+
226
+ def <=>(other)
227
+ self.name <=> other.name
228
+ end
229
+
230
+ def ==(other)
231
+ other.respond_to?(:values) ? self.values[:id] == other.values[:id] : super
232
+ end
233
+
234
+ def parse(s)
235
+ return if inflib.nil?
236
+
237
+ semaphore.synchronize do
238
+ inflib.parse s
239
+ end
240
+ end
241
+
242
+ def invoke(a, *args)
243
+ return if inflib.nil?
244
+
245
+ inflib.invoke(a, *args)
246
+ end
247
+
248
+ def _invoke(a, *args)
249
+ return if inflib.nil?
250
+
251
+ inflib._invoke(a, *args)
252
+ end
253
+
254
+ def __invoke(a, *args)
255
+ return if inflib.nil?
256
+
257
+ inflib.__invoke(a, *args)
258
+ end
259
+
260
+ def inflib
261
+ return InformLibrary[self] if self.has?(:animate) ||
262
+ defined?(Character) && self.is_a?(Character)
263
+
264
+ @inflib
265
+ end
266
+
267
+ # rubocop: disable Style/TrivialAccessors
268
+ def inflib=(inflib)
269
+ @inflib = inflib
270
+ end
271
+ # rubocop: enable Style/TrivialAccessors
272
+
273
+ def print_zmachine_result(s, should_prompt: true)
274
+ inflib&.print_zmachine_result(s, should_prompt:)
275
+ end
276
+
277
+ def print_evented_zmachine_result(s, isolate: false)
278
+ inflib&.print_evented_zmachine_result(s, isolate: isolate)
279
+ end
280
+
281
+ Visited = defined?(ConcurrentHashMap) ? ConcurrentHashMap.new : {}
282
+
283
+ def visited
284
+ Visited[self] ||= []
285
+ end
286
+
287
+ def after_create
288
+ super
289
+ index_words if respond_to?(:index_words)
290
+ init
291
+ end
292
+
293
+ def init
294
+ # A designer should implement this in a subclass if necessary
295
+ end
296
+
297
+ def safe_refresh
298
+ self.refresh
299
+ rescue Sequel::NoExistingObject => e
300
+ log.warn "Object refresh failure: #{e.message}; ignoring"
301
+ end
302
+
303
+ def safe_save
304
+ self.save
305
+ rescue Sequel::NoExistingObject => e
306
+ log.warn "Object save failure: #{e.message}; ignoring"
307
+ end
308
+
309
+ def safe_init
310
+ self.init if self.respond_to? :init
311
+ rescue Sequel::NoExistingObject => e
312
+ log.warn "Object initialization failure: #{e.message}; ignoring"
313
+ end
314
+
315
+ def classify_as(klass)
316
+ return if klass.nil?
317
+
318
+ klass = find_class klass unless klass.is_a? Class
319
+ self.object_type = klass.name
320
+ self.safe_save
321
+ self.safe_init
322
+ o = Inform::Object[self.id]
323
+ log.debug "o.object_type: #{o.object_type}"
324
+ return o
325
+ end
326
+
327
+ # def ephemeral_children
328
+ # EphemeralObjectsChildren[identity] ||= []
329
+ # end
330
+
331
+ # # TODO: Implement a unit test for this
332
+ # def <<(o)
333
+ # return if o.nil?
334
+ # return if o == self
335
+
336
+ # if o.ephemeral?
337
+ # o.parent = self
338
+ # ephemeral_children << o
339
+ # else
340
+ # self.add_child o
341
+ # self.save
342
+ # self.refresh
343
+ # end
344
+ # end
345
+
346
+ def ephemeral_children
347
+ EphemeralObjectsChildren[identity] ||= []
348
+ end
349
+
350
+ # rubocop: disable Metrics/AbcSize
351
+ # rubocop: disable Metrics/CyclomaticComplexity
352
+ # rubocop: disable Metrics/MethodLength
353
+ # rubocop: disable Metrics/PerceivedComplexity
354
+ # TODO: Implement a unit test for this
355
+ def <<(o)
356
+ return if o.nil?
357
+ return if o == self
358
+
359
+ if o.ephemeral?
360
+ o.parent = self
361
+ ephemeral_children << o
362
+ else
363
+ self.add_child o if self.respond_to?(:add_child)
364
+ o.parent_id = self.id if o.respond_to?(:parent_id=)
365
+ o.save_changes if o.respond_to?(:save_changes)
366
+ self.save
367
+ self.associations.delete(:children) if self.respond_to?(:associations)
368
+ self.refresh
369
+ end
370
+ end
371
+ # rubocop: enable Metrics/AbcSize
372
+ # rubocop: enable Metrics/CyclomaticComplexity
373
+ # rubocop: enable Metrics/MethodLength
374
+ # rubocop: enable Metrics/PerceivedComplexity
375
+
376
+ def remove
377
+ self.parent.remove_child(self.id) if self.parent&.children&.include?(self)
378
+ rescue StandardError => e
379
+ log.error e
380
+ end
381
+
382
+ def empty?
383
+ return false if ephemeral_children.any?
384
+ return children_dataset.count.zero? if self.respond_to?(:children_dataset)
385
+
386
+ children.empty?
387
+ rescue StandardError => e
388
+ log.error "Unexpected error getting children count for #{self.class} #{self.id}: #{e.message}", $ERROR_INFO
389
+ children.empty?
390
+ end
391
+ # def empty?
392
+ # begin
393
+ # n = db.fetch("select count(*) from object where parent_id = #{self.id}").first[:count].to_i
394
+ # rescue StandardError => e
395
+ # log.error "Unexpected error getting children count for #{self.class} #{self.id}: #{e.message}", $ERROR_INFO
396
+ # n = children.length
397
+ # end
398
+ # n == 0
399
+ # end
400
+
401
+ def child(o = nil)
402
+ return o.child unless o.nil?
403
+ return self.children_dataset.first if self.respond_to?(:children_dataset)
404
+
405
+ self.children.first
406
+ end
407
+
408
+ def location
409
+ linkto(:location) || self.parent
410
+ end
411
+
412
+ def list_together
413
+ linkto :list_together
414
+ end
415
+
416
+ def before_create
417
+ self.created_at ||= Time.now
418
+ super
419
+ end
420
+
421
+ def before_destroy
422
+ self.safe_refresh if self.respond_to? :safe_refresh
423
+ self.children.each { |x| x.destroy unless x.has? :prized }
424
+ super
425
+ end
426
+
427
+ def after_destroy
428
+ super
429
+ end
430
+
431
+ def after_save
432
+ super
433
+ self.modified_at = Time.now
434
+ end
435
+
436
+ def before_clone(copy); end
437
+
438
+ def after_clone(copy)
439
+ copy.untag :prized
440
+ end
441
+
442
+ # rubocop: disable Metrics/AbcSize
443
+ # rubocop: disable Metrics/MethodLength
444
+ def clone
445
+ return if Session.players.include? self # Don't clone players.
446
+
447
+ data = self.values.dup
448
+ data.delete :id
449
+ data.delete :attributes
450
+ data.delete :properties
451
+ copy = self.class.new(data)
452
+ before_clone(copy) if respond_to? :before_clone
453
+ copy.properties = self.properties
454
+ copy.save
455
+ copy.untag(*copy.tags)
456
+ copy.tag(*self.tags)
457
+ copy.mod(*self.modules)
458
+ copy.link(:original, self)
459
+ self.children.each { |x| copy << x.clone }
460
+ after_clone(copy) if respond_to? :after_clone
461
+ copy
462
+ end
463
+ # rubocop: enable Metrics/AbcSize
464
+ # rubocop: enable Metrics/MethodLength
465
+
466
+ def sysclone(klass = Inform::System::Object)
467
+ copy = klass.new self.short_name
468
+ copy.name = self.name
469
+ copy.properties[:source_id] = self.id
470
+ copy.properties = self.properties
471
+ copy.tags = self.nil_safe_tags
472
+ after_clone(copy) if respond_to? :after_clone
473
+ copy
474
+ end
475
+
476
+ ExportFileNameTemplate = '%<name>s-%<id>s.%<ext>s'.freeze
477
+ JsonOptions = {
478
+ include: %i[tagged modularized]
479
+ }.freeze
480
+
481
+ def export_json
482
+ return unless respond_to? :to_json
483
+
484
+ self.to_json(**JsonOptions).save(
485
+ format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :json)
486
+ )
487
+ end
488
+
489
+ def export_yaml
490
+ return unless self.respond_to? :to_yaml
491
+
492
+ self.to_yaml.save(
493
+ format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :yaml)
494
+ )
495
+ end
496
+
497
+ XmlOptions = {
498
+ except: :id,
499
+ include: {
500
+ tagged: {
501
+ except: :id
502
+ },
503
+ modularized: {
504
+ except: :id
505
+ }
506
+ }
507
+ }.freeze
508
+ XmlFileNameTemplate = '%<name>s-%<id>s.json'.freeze
509
+
510
+ def export_xml
511
+ return unless self.respond_to? :to_xml
512
+
513
+ self.to_xml(**XmlOptions).save(
514
+ format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :xml)
515
+ )
516
+ end
517
+
518
+ def encode_with(coder)
519
+ %w[id name short_name description properties links modularized tagged].each do |v|
520
+ coder[v] = self.send(v) if self.respond_to? v
521
+ end
522
+ end
523
+
524
+ # def init_with(coder)
525
+ # %w[ id name short_name description properties ].each do |v|
526
+ # m = "#{v}=".to_sym
527
+ # self.send(m, coder[v]) if self.respond_to? m
528
+ # end
529
+ # log.debug "Importing #{self.inspect}"
530
+ # log.debug "Importing #{self.to_hash.inspect}"
531
+ # log.debug " - #{(self.methods - ::Object.instance_methods).uniq.sort}"
532
+ # self
533
+ # end
534
+ end
535
+ # rubocop: enable Metrics/ClassLength
536
+ # class Object
537
+ end
538
+ # module Inform
539
+
540
+ # The ExtendedProperties class
541
+ class ExtendedProperties < Inform::Object
542
+ def init
543
+ has :proper
544
+ super
545
+ end
546
+ end
@@ -0,0 +1,152 @@
1
+ # lib/story_teller/inform/relational/tag.rb
2
+ # encoding: utf-8
3
+ # frozen_string_literal: false
4
+
5
+ # Copyright Nels Nelson 2008-2025 but freely usable (see license)
6
+ #
7
+ # This file is part of the StoryTeller.
8
+ #
9
+ # The StoryTeller is free software: you can redistribute it and/or
10
+ # modify it under the terms of the GNU General Public License as published
11
+ # by the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # The StoryTeller is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License
20
+ # along with the StoryTeller. If not, see <http://www.gnu.org/licenses/>.
21
+
22
+ # Tag
23
+
24
+ if defined?(Sequel::Migration)
25
+ # The TagSetup class
26
+ class TagSetup < Sequel::Migration
27
+ def up
28
+ # return if table_exists? :tag
29
+ log.debug "#up"
30
+ create_table? :tag do
31
+ primary_key :id
32
+ index :name
33
+ index :created_at
34
+
35
+ String :name, unique: true, null: false
36
+ DateTime :created_at
37
+ DateTime :modified_at
38
+ end
39
+ end
40
+
41
+ def down
42
+ drop_table(:tag, cascade: true) if table_exists? :tag
43
+ end
44
+ end
45
+ # class TagSetup
46
+ end
47
+ # defined?(Sequel::Migration)
48
+
49
+ # module Inform
50
+ module Inform
51
+ # class Tag
52
+ class Tag < Sequel::Model
53
+ set_primary_key :id
54
+ def_column_accessor :created_at, :modified_at
55
+ def_column_accessor :name
56
+
57
+ def before_create
58
+ self.created_at ||= Time.now
59
+ super
60
+ end
61
+
62
+ def after_create
63
+ super
64
+ Inform.attributes.reset
65
+ end
66
+
67
+ def after_save
68
+ super
69
+ self.modified_at = Time.now.utc
70
+ end
71
+
72
+ def to_s
73
+ name
74
+ end
75
+ alias to_str to_s
76
+
77
+ def <=>(other)
78
+ self.name <=> other.name
79
+ end
80
+
81
+ def self.tidy
82
+ db << %(delete from tag where id in
83
+ (select a.id from tag a group by a.id, a.name
84
+ having ((select count(b.id) from tagged b
85
+ where b.tag_id = a.id) = 0)))
86
+ Inform.attributes.reset
87
+ return nil
88
+ end
89
+
90
+ # rubocop: disable Style/FormatStringToken
91
+ def self.dirty
92
+ results = db.fetch %(select a.id, a.name from tag a group by a.id, a.name having
93
+ ((select count(b.id) from tagged b where b.tag_id = a.id) = 0))
94
+ return nil if results.empty?
95
+ s = [format('%5s %20s', *results.first.keys)]
96
+ s.concat(results.collect { |row| format('%5d %20s', *row.values) })
97
+ s.join("\n")
98
+ end
99
+ # rubocop: enable Style/FormatStringToken
100
+
101
+ # rubocop: disable Style/FormatStringToken
102
+ def self.stats
103
+ results = db.fetch %(select a.*, count(a.id) as "number tagged"
104
+ from tag a, tagged b where b.tag_id = a.id group by a.id, a.name)
105
+ return nil if results.empty?
106
+ s = [format('%5s %20s %15s', *results.first.keys)]
107
+ s.concat(results.collect { |row| format('%5d %20s %15s', *row.values) })
108
+ s.join("\n")
109
+ end
110
+ # rubocop: enable Style/FormatStringToken
111
+ end
112
+ end
113
+ # module Inform
114
+
115
+ # module Inform
116
+ module Inform
117
+ module_function
118
+
119
+ def tag_klass
120
+ Inform::Tag
121
+ end
122
+ end
123
+
124
+ # Tagged
125
+
126
+ if defined?(Sequel::Migration)
127
+ # The TaggedSetup class
128
+ class TaggedSetup < Sequel::Migration
129
+ def up
130
+ log.debug "#up"
131
+ create_table? :tagged do
132
+ primary_key :id
133
+ foreign_key :object_id, :object, on_delete: :cascade
134
+ foreign_key :tag_id, :tag, on_delete: :cascade
135
+ end
136
+ end
137
+
138
+ def down
139
+ drop_table(:tagged, cascade: true) if table_exists? :tagged
140
+ end
141
+ end
142
+ # class TaggedSetup
143
+ end
144
+ # defined?(Sequel::Migration)
145
+
146
+ # module Inform
147
+ module Inform
148
+ # The Tagged class
149
+ class Tagged < Sequel::Model
150
+ set_primary_key :id
151
+ end
152
+ end