Class: Inform::Object

Inherits:
Sequel::Model show all
Includes:
Context, Events, Genealogical, Linkable, Modular, Prototypical, Taggable, StoryTeller::Builtins, StoryTeller::Daemons, StoryTeller::IO, StoryTeller::Publisher
Defined in:
lib/story_teller/inform/models.rb,
lib/story_teller/inform/relational/object.rb

Overview

The Inform::Object class rubocop: disable Metrics/ClassLength

Direct Known Subclasses

ExtendedProperties, StoryTeller::Character

Constant Summary collapse

Visited =
defined?(ConcurrentHashMap) ? ConcurrentHashMap.new : {}
ExportFileNameTemplate =
'%<name>s-%<id>s.%<ext>s'.freeze
JsonOptions =
{
  include: %i[tagged modularized]
}.freeze
XmlOptions =
{
  except: :id,
  include: {
    tagged: {
      except: :id
    },
    modularized: {
      except: :id
    }
  }
}.freeze
XmlFileNameTemplate =
'%<name>s-%<id>s.json'.freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Modular

#after_initialize, #apply_modules, #mod, #modules, #modules_semaphore, #nil_safe_modules, #reset_modules, #unmod

Methods included from Linkable

#_set_object, #find_link, #link, #linked?, #links, #linksfrom, #linkto, #unlink

Methods inherited from Sequel::Model

implicit_table_name

Methods included from StoryTeller::InheritanceListener

included

Constructor Details

#initialize(*args, &block) ⇒ Object

rubocop: disable Metrics/MethodLength



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/story_teller/inform/relational/object.rb', line 103

def initialize(*args, &block)
  if args.empty?
    super(&nil)
  elsif args.first.is_a?(String)
    short_name = args.first
    super({ name: short_name, short_name: short_name }, &nil)
  else
    super(*args, &nil)
  end
  save
  @tags_semaphore = Mutex.new
  @semaphore = Mutex.new
  self.with(&block) unless block.nil?
  log.debug "Initializing #{self}"
end

Class Method Details

.fetch_or_create_by_name(name) ⇒ Object

rubocop: enable Metrics/MethodLength



120
121
122
123
124
125
# File 'lib/story_teller/inform/relational/object.rb', line 120

def self.fetch_or_create_by_name(name)
  key = name.to_s
  object = first(name: key)
  object ||= all.find { |candidate| candidate.name_values.include?(key) }
  object || new(key)
end

Instance Method Details

#<<(o) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/CyclomaticComplexity rubocop: disable Metrics/MethodLength rubocop: disable Metrics/PerceivedComplexity TODO: Implement a unit test for this



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/story_teller/inform/relational/object.rb', line 355

def <<(o)
  return if o.nil?
  return if o == self

  if o.ephemeral?
    o.parent = self
    ephemeral_children << o
  else
    self.add_child o if self.respond_to?(:add_child)
    o.parent_id = self.id if o.respond_to?(:parent_id=)
    o.save_changes if o.respond_to?(:save_changes)
    self.save
    self.associations.delete(:children) if self.respond_to?(:associations)
    self.refresh
  end
end

#<=>(other) ⇒ Object



226
227
228
# File 'lib/story_teller/inform/relational/object.rb', line 226

def <=>(other)
  self.name <=> other.name
end

#==(other) ⇒ Object



230
231
232
# File 'lib/story_teller/inform/relational/object.rb', line 230

def ==(other)
  other.respond_to?(:values) ? self.values[:id] == other.values[:id] : super
end

#__invoke(a, *args) ⇒ Object



254
255
256
257
258
# File 'lib/story_teller/inform/relational/object.rb', line 254

def __invoke(a, *args)
  return if inflib.nil?

  inflib.__invoke(a, *args)
end

#_invoke(a, *args) ⇒ Object



248
249
250
251
252
# File 'lib/story_teller/inform/relational/object.rb', line 248

def _invoke(a, *args)
  return if inflib.nil?

  inflib._invoke(a, *args)
end

#after_clone(copy) ⇒ Object



438
439
440
# File 'lib/story_teller/inform/relational/object.rb', line 438

def after_clone(copy)
  copy.untag :prized
end

#after_createObject



287
288
289
290
291
# File 'lib/story_teller/inform/relational/object.rb', line 287

def after_create
  super
  index_words if respond_to?(:index_words)
  init
end

#after_destroyObject



427
428
429
# File 'lib/story_teller/inform/relational/object.rb', line 427

def after_destroy
  super
end

#after_saveObject



431
432
433
434
# File 'lib/story_teller/inform/relational/object.rb', line 431

def after_save
  super
  self.modified_at = Time.now
end

#before_clone(copy) ⇒ Object



436
# File 'lib/story_teller/inform/relational/object.rb', line 436

def before_clone(copy); end

#before_createObject



416
417
418
419
# File 'lib/story_teller/inform/relational/object.rb', line 416

def before_create
  self.created_at ||= Time.now
  super
end

#before_destroyObject



421
422
423
424
425
# File 'lib/story_teller/inform/relational/object.rb', line 421

def before_destroy
  self.safe_refresh if self.respond_to? :safe_refresh
  self.children.each { |x| x.destroy unless x.has? :prized }
  super
end

#child(o = nil) ⇒ Object

def empty?

begin
  n = db.fetch("select count(*) from object where parent_id = #{self.id}").first[:count].to_i
rescue StandardError => e
  log.error "Unexpected error getting children count for #{self.class} #{self.id}: #{e.message}", $ERROR_INFO
  n = children.length
end
n == 0

end



401
402
403
404
405
406
# File 'lib/story_teller/inform/relational/object.rb', line 401

def child(o = nil)
  return o.child unless o.nil?
  return self.children_dataset.first if self.respond_to?(:children_dataset)

  self.children.first
end

#classify_as(klass) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
# File 'lib/story_teller/inform/relational/object.rb', line 315

def classify_as(klass)
  return if klass.nil?

  klass = find_class klass unless klass.is_a? Class
  self.object_type = klass.name
  self.safe_save
  self.safe_init
  o = Inform::Object[self.id]
  log.debug "o.object_type: #{o.object_type}"
  return o
end

#cloneObject

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/story_teller/inform/relational/object.rb', line 444

def clone
  return if Session.players.include? self # Don't clone players.

  data = self.values.dup
  data.delete :id
  data.delete :attributes
  data.delete :properties
  copy = self.class.new(data)
  before_clone(copy) if respond_to? :before_clone
  copy.properties = self.properties
  copy.save
  copy.untag(*copy.tags)
  copy.tag(*self.tags)
  copy.mod(*self.modules)
  copy.link(:original, self)
  self.children.each { |x| copy << x.clone }
  after_clone(copy) if respond_to? :after_clone
  copy
end

#description(*args) ⇒ Object

alias description= description



194
195
196
197
198
199
200
201
# File 'lib/story_teller/inform/relational/object.rb', line 194

def description(*args)
  return self[:description] if args.empty?

  value = args.length > 1 ? args.join : args.first
  self[:description] = value
  save_changes if respond_to?(:save_changes)
  value
end

#description=(value) ⇒ Object



203
204
205
206
# File 'lib/story_teller/inform/relational/object.rb', line 203

def description=(value)
  self[:description] = value
  save_changes if respond_to?(:save_changes)
end

#empty?Boolean

Returns:

  • (Boolean)


382
383
384
385
386
387
388
389
390
# File 'lib/story_teller/inform/relational/object.rb', line 382

def empty?
  return false if ephemeral_children.any?
  return children_dataset.count.zero? if self.respond_to?(:children_dataset)

  children.empty?
rescue StandardError => e
  log.error "Unexpected error getting children count for #{self.class} #{self.id}: #{e.message}", $ERROR_INFO
  children.empty?
end

#encode_with(coder) ⇒ Object



518
519
520
521
522
# File 'lib/story_teller/inform/relational/object.rb', line 518

def encode_with(coder)
  %w[id name short_name description properties links modularized tagged].each do |v|
    coder[v] = self.send(v) if self.respond_to? v
  end
end

#ephemeral_childrenObject

if o.ephemeral?

  o.parent = self
  ephemeral_children << o
else
  self.add_child o
  self.save
  self.refresh
end

end



346
347
348
# File 'lib/story_teller/inform/relational/object.rb', line 346

def ephemeral_children
  EphemeralObjectsChildren[identity] ||= []
end

#export_jsonObject



481
482
483
484
485
486
487
# File 'lib/story_teller/inform/relational/object.rb', line 481

def export_json
  return unless respond_to? :to_json

  self.to_json(**JsonOptions).save(
    format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :json)
  )
end

#export_xmlObject



510
511
512
513
514
515
516
# File 'lib/story_teller/inform/relational/object.rb', line 510

def export_xml
  return unless self.respond_to? :to_xml

  self.to_xml(**XmlOptions).save(
    format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :xml)
  )
end

#export_yamlObject



489
490
491
492
493
494
495
# File 'lib/story_teller/inform/relational/object.rb', line 489

def export_yaml
  return unless self.respond_to? :to_yaml

  self.to_yaml.save(
    format(ExportFileNameTemplate, name: self.to_s, id: self.id, ext: :yaml)
  )
end

#inflibObject



260
261
262
263
264
265
# File 'lib/story_teller/inform/relational/object.rb', line 260

def inflib
  return InformLibrary[self] if self.has?(:animate) ||
    defined?(Character) && self.is_a?(Character)

  @inflib
end

#inflib=(inflib) ⇒ Object

rubocop: disable Style/TrivialAccessors



268
269
270
# File 'lib/story_teller/inform/relational/object.rb', line 268

def inflib=(inflib)
  @inflib = inflib
end

#initObject



293
294
295
# File 'lib/story_teller/inform/relational/object.rb', line 293

def init
  # A designer should implement this in a subclass if necessary
end

#invoke(a, *args) ⇒ Object



242
243
244
245
246
# File 'lib/story_teller/inform/relational/object.rb', line 242

def invoke(a, *args)
  return if inflib.nil?

  inflib.invoke(a, *args)
end

#list_togetherObject



412
413
414
# File 'lib/story_teller/inform/relational/object.rb', line 412

def list_together
  linkto :list_together
end

#locationObject



408
409
410
# File 'lib/story_teller/inform/relational/object.rb', line 408

def location
  linkto(:location) || self.parent
end

#name(*args) ⇒ Object

self.values = args.length > 1 ? args.join(‘ ’) : args

self.save
self.values[:name]

end



139
140
141
142
143
144
145
146
147
# File 'lib/story_teller/inform/relational/object.rb', line 139

def name(*args)
  return args.first.object_name if args.first.respond_to?(:object_name)
  return self.values[:name] if args.empty?

  names = args.flatten
  self.values[:name] = names
  self.save
  self.values[:name] = names
end

#name=(value) ⇒ Object



149
150
151
# File 'lib/story_teller/inform/relational/object.rb', line 149

def name=(value)
  name(value)
end

#name_valuesObject



127
128
129
# File 'lib/story_teller/inform/relational/object.rb', line 127

def name_values
  Array(self.values[:name]).flatten.map(&:to_s)
end

#name_wordsObject

def name_words

self.values[:name].split(/[,\s]+/).join(', ')

end



156
157
158
# File 'lib/story_teller/inform/relational/object.rb', line 156

def name_words
  name_values.join(', ')
end

#object_nameObject

rubocop: enable Metrics/MethodLength



179
180
181
# File 'lib/story_teller/inform/relational/object.rb', line 179

def object_name
  self.values[:display_name] || self.values[:short_name] || self.values[:name]
end

#parse(s) ⇒ Object



234
235
236
237
238
239
240
# File 'lib/story_teller/inform/relational/object.rb', line 234

def parse(s)
  return if inflib.nil?

  semaphore.synchronize do
    inflib.parse s
  end
end


277
278
279
# File 'lib/story_teller/inform/relational/object.rb', line 277

def print_evented_zmachine_result(s, isolate: false)
  inflib&.print_evented_zmachine_result(s, isolate: isolate)
end

rubocop: enable Style/TrivialAccessors



273
274
275
# File 'lib/story_teller/inform/relational/object.rb', line 273

def print_zmachine_result(s, should_prompt: true)
  inflib&.print_zmachine_result(s, should_prompt:)
end

#removeObject

rubocop: enable Metrics/AbcSize rubocop: enable Metrics/CyclomaticComplexity rubocop: enable Metrics/MethodLength rubocop: enable Metrics/PerceivedComplexity



376
377
378
379
380
# File 'lib/story_teller/inform/relational/object.rb', line 376

def remove
  self.parent.remove_child(self.id) if self.parent&.children&.include?(self)
rescue StandardError => e
  log.error e
end

#routinesObject



222
223
224
# File 'lib/story_teller/inform/relational/object.rb', line 222

def routines
  ((self.methods - Object.instance_methods) - Inform::Object.instance_methods).sort.uniq
end

#safe_initObject



309
310
311
312
313
# File 'lib/story_teller/inform/relational/object.rb', line 309

def safe_init
  self.init if self.respond_to? :init
rescue Sequel::NoExistingObject => e
  log.warn "Object initialization failure: #{e.message}; ignoring"
end

#safe_refreshObject



297
298
299
300
301
# File 'lib/story_teller/inform/relational/object.rb', line 297

def safe_refresh
  self.refresh
rescue Sequel::NoExistingObject => e
  log.warn "Object refresh failure: #{e.message}; ignoring"
end

#safe_saveObject



303
304
305
306
307
# File 'lib/story_teller/inform/relational/object.rb', line 303

def safe_save
  self.save
rescue Sequel::NoExistingObject => e
  log.warn "Object save failure: #{e.message}; ignoring"
end

#semaphoreObject



208
209
210
# File 'lib/story_teller/inform/relational/object.rb', line 208

def semaphore
  @semaphore ||= Mutex.new
end

#short_name(*args) ⇒ Object

rubocop: disable Metrics/MethodLength



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/story_teller/inform/relational/object.rb', line 161

def short_name(*args)
  unless args.empty?
    self.short_name = args.first
    return
  end

  textual_name = self.values[:short_name]
  synonyms = self.values[:name]
  if textual_name.nil? || (textual_name.respond_to?(:empty?) && textual_name.empty?)
    self.values[:short_name] = synonyms
    self.save
    self.values[:short_name]
  else
    textual_name
  end
end

#sysclone(klass = Inform::System::Object) ⇒ Object

rubocop: enable Metrics/AbcSize rubocop: enable Metrics/MethodLength



466
467
468
469
470
471
472
473
474
# File 'lib/story_teller/inform/relational/object.rb', line 466

def sysclone(klass = Inform::System::Object)
  copy = klass.new self.short_name
  copy.name = self.name
  copy.properties[:source_id] = self.id
  copy.properties = self.properties
  copy.tags = self.nil_safe_tags
  after_clone(copy) if respond_to? :after_clone
  copy
end

#tagsObject



218
219
220
# File 'lib/story_teller/inform/relational/object.rb', line 218

def tags
  tags_semaphore.synchronize { tags_original }
end

#tags_originalObject



216
# File 'lib/story_teller/inform/relational/object.rb', line 216

alias tags_original tags

#tags_semaphoreObject



212
213
214
# File 'lib/story_teller/inform/relational/object.rb', line 212

def tags_semaphore
  @tags_semaphore ||= Mutex.new
end

#visitedObject



283
284
285
# File 'lib/story_teller/inform/relational/object.rb', line 283

def visited
  Visited[self] ||= []
end