Class: VirtualClass

Inherits:
Role
  • Object
show all
Includes:
Property::StoredSchema, Zena::Use::Fulltext::VirtualClassMethods, Zena::Use::PropEval::VirtualClassMethods, Zena::Use::Relations::ClassMethods, Zena::Use::ScopeIndex::VirtualClassMethods
Defined in:
app/models/virtual_class.rb

Overview

The virtual class holds type information and other attributes to build indices and computed properties. This class acts as the “schema” for nodes.

Since this class also uses Property to store some of it’s data, confusion must not be made between the VirtualClass as a schema (containing Node property definitions) and the VirtualClass’ own schema.

The roles in the vclass contain self, super and all the attached roles like this (roles for the Letter virtual class):

 [
   <VirtualClass:'Letter' paper, search_mono, search>,
   [
     <VirtualClass:'Note' >,
-->  <Role:'Note' >,
     [
       <VirtualClass:'Node' >,
-->    <Role:'Node' cached_role_ids, title, text, summary>,
       <Role:'Original' weight, origin, tz>,
       <Role:'Task' assigned>
     ]
   ]
 ]

Elements marked with ‘–>’ above are the ‘schema’ roles used by the real classes to store ruby declared properties. Since Zena is multi-site, there is one VirtualClass instance of the real classes for each site: this is why the ruby declarations are not stored in the VirtualClass itself for real classes.

Defined Under Namespace

Classes: Cache

Class Attribute Summary collapse

Instance Attribute Summary collapse

Attributes inherited from Role

#klass

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Zena::Use::ScopeIndex::VirtualClassMethods

included

Methods included from Zena::Use::PropEval::VirtualClassMethods

included

Methods included from Zena::Use::Fulltext::VirtualClassMethods

included

Methods included from Zena::Use::Relations::ClassMethods

#all_relations

Methods inherited from Role

#defined_safe_column, export, #icon=, import, #import_columns, #import_relations, #superclass=

Class Attribute Details

.caches_by_siteObject

Returns the value of attribute caches_by_site.



208
209
210
# File 'app/models/virtual_class.rb', line 208

def caches_by_site
  @caches_by_site
end

.export_attributesObject

Returns the value of attribute export_attributes.



34
35
36
# File 'app/models/virtual_class.rb', line 34

def export_attributes
  @export_attributes
end

Instance Attribute Details

#import_resultObject

Returns the value of attribute import_result.



39
40
41
# File 'app/models/virtual_class.rb', line 39

def import_result
  @import_result
end

Class Method Details

.[](name) ⇒ Object



210
211
212
# File 'app/models/virtual_class.rb', line 210

def [](name)
  find_by_name(name)
end

.all_classes(base_kpath = 'N', without_list = nil) ⇒ Object



231
232
233
# File 'app/models/virtual_class.rb', line 231

def all_classes(base_kpath = 'N', without_list = nil)
  (self.caches_by_site[current_site.id] ||= Cache.new).all_classes(base_kpath, without_list)
end

.expire_cache!Object



226
227
228
229
# File 'app/models/virtual_class.rb', line 226

def expire_cache!
  Zena::Db.set_attribute(current_site, 'roles_updated_at', Time.now.utc)
  self.caches_by_site[current_site.id] = Cache.new
end

.find_by_id(id) ⇒ Object



214
215
216
# File 'app/models/virtual_class.rb', line 214

def find_by_id(id)
  (self.caches_by_site[current_site.id] ||= Cache.new).find_by_id(id)
end

.find_by_kpath(kpath) ⇒ Object



218
219
220
# File 'app/models/virtual_class.rb', line 218

def find_by_kpath(kpath)
  (self.caches_by_site[current_site.id] ||= Cache.new).find_by_kpath(kpath)
end

.find_by_name(name) ⇒ Object



222
223
224
# File 'app/models/virtual_class.rb', line 222

def find_by_name(name)
  (self.caches_by_site[current_site.id] ||= Cache.new).find_by_name(name)
end

.safe_method_type(signature, receiver) ⇒ Object

Methods on VirtualClass instances, not nodes.



66
67
68
69
70
71
72
# File 'app/models/virtual_class.rb', line 66

def self.safe_method_type(signature, receiver)
  if signature.first == 'new'
    {:method => 'zafu_new', :class => receiver.literal}
  else
    super
  end
end

.split_kpath(kpath) ⇒ Object

Class path hierarchy. Example for (Post) : N, NN, NNP



75
76
77
78
79
# File 'app/models/virtual_class.rb', line 75

def self.split_kpath(kpath)
  parts = []
  kpath.split(//).each_index { |i| parts << kpath[0..i] }
  parts
end

Instance Method Details

#<(other_class) ⇒ Object

Test ancestry



485
486
487
488
489
490
491
492
493
494
495
496
# File 'app/models/virtual_class.rb', line 485

def <(other_class)
  if other_class.kind_of?(VirtualClass)
    kpath = other_class.kpath
    self.kpath != kpath && self.kpath[0..(kpath.length-1)] == kpath
  elsif real_class.kpath != self.kpath
    # Sub class of real_class
    real_class <= other_class
  else
    # VirtualClass of the real_class
    real_class < other_class
  end
end

#<=(other_class) ⇒ Object

Test ancestry



475
476
477
478
479
480
481
482
# File 'app/models/virtual_class.rb', line 475

def <=(other_class)
  if other_class.kind_of?(VirtualClass)
    kpath = other_class.kpath
    self.kpath[0..(kpath.length-1)] == kpath
  else
    real_class <= other_class
  end
end

#ancestorsObject

This is used by RubyLess in method signatures: [:zen_path, #<VirtualClass ‘Post’>] —> [:zen_path, Node]



470
471
472
# File 'app/models/virtual_class.rb', line 470

def ancestors
  @ancestors ||= [real_class] + real_class.ancestors
end

#build_query(*args) ⇒ Object

Build SQLiss query.



505
506
507
# File 'app/models/virtual_class.rb', line 505

def build_query(*args)
  real_class.build_query(*args)
end

#classes_for_form(opts = {}) ⇒ Object

FIXME: how to make sure all sub-classes of Node are loaded before this is called ? TODO: move into helper



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'app/models/virtual_class.rb', line 240

def classes_for_form(opts={})
  group_ids = visitor.group_ids
  if klass = opts.delete(:class)
    if klass = VirtualClass[klass]
      base_kpath = klass.kpath
    else
      base_kpath = self.kpath
    end
  else
    base_kpath = self.kpath
  end

  kpath_len = base_kpath.size

  attribute = opts[:class_attr] || 'name'

  VirtualClass.all_classes(base_kpath, opts[:without]).map do |vclass|
    # Only insert allowed classes.
    if vclass.create_group_id.nil? || group_ids.include?(vclass.create_group_id)
      # white spaces are insecable spaces (not ' ')
      a, b = vclass.kpath, vclass.name
      [('  ' * (a.size - kpath_len)) + b, vclass[attribute]]
    else
      nil
    end
  end.compact
end

#content_type_reObject



272
273
274
275
# File 'app/models/virtual_class.rb', line 272

def content_type_re
  # if content_type is empty => match all
  @content_type_re ||= %r{^#{content_type || ".*"}$}
end

#create_instance(*args) ⇒ Object

Create new nodes instances of this VirtualClass



525
526
527
528
529
# File 'app/models/virtual_class.rb', line 525

def create_instance(*args)
  obj = self.new_instance(*args)
  obj.save
  obj
end

#defined_safe_columnsObject

Return safe columns including super class’s safe columns



410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'app/models/virtual_class.rb', line 410

def defined_safe_columns
  @defined_safe_columns ||= if real_class?
    # Get columns from the 'native' schema of the real class (this schema is a Property::Role,
    # not a VirtualClass or ::Role).
    #
    # Only columns explicitly declared safe are safe here
    real_class.schema.defined_columns.values.select do |col|
      real_class.safe_method_type([col.name])
    end.sort {|a,b| a.name <=> b.name}
  else
    super
  end
end

#do_find(*args) ⇒ Object

Execute find



510
511
512
# File 'app/models/virtual_class.rb', line 510

def do_find(*args)
  real_class.do_find(*args)
end

#exportObject



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'app/models/virtual_class.rb', line 298

def export
  res = super
  self.class.export_attributes.each do |k|
    value = self.send(k)
    next if value.blank?
    res[k] = value
  end
  subclasses = secure(::Role) do
    ::Role.find(:all, :conditions => [
      '(kpath LIKE ? and type = ?) OR (kpath = ? AND id <> ?)', "#{kpath}_", "VirtualClass", kpath, self.id.to_i
    ], :order => 'kpath ASC')
  end
  if real_class?
    # insert native subclasses
    Node.native_classes.each do |kpath, klass|
      if kpath =~ /\A#{self.kpath}.\Z/
        subclasses ||= []
        subclasses << VirtualClass[klass.name]
      end
    end

    if subclasses
      subclasses.sort! do |a,b|
        if a.class != b.class
          # Roles before subclasses
          b.class <=> a.class
        else
          a.kpath <=> b.kpath
        end
      end
    end
  end

  if subclasses
    subclasses.each do |sub|
      res[sub.name] = sub.export
    end
  end

  relations = Relation.all(
    :conditions => ['source_kpath = ? AND site_id = ?', kpath, site_id],
    :order      => 'target_role ASC'
  )
  if !relations.empty?
    res['relations'] = list = Zafu::OrderedHash.new
    relations.each do |rel|
      list[rel.target_role] = rel.export
    end
  end
  res
end

#filtered_relations(group_filter) ⇒ Object

List all relations that can be set for this class, filtering by relation group.



545
546
547
# File 'app/models/virtual_class.rb', line 545

def filtered_relations(group_filter)
  all_relations(nil, group_filter)
end

#index_groupsObject

Cache index groups



550
551
552
# File 'app/models/virtual_class.rb', line 550

def index_groups
  @index_groups ||= super
end

#kpath_match?(kpath) ⇒ Boolean

check inheritance chain through kpath

Returns:

  • (Boolean)


351
352
353
# File 'app/models/virtual_class.rb', line 351

def kpath_match?(kpath)
  self.kpath =~ /^#{kpath}/
end

#load_attached_roles!Object

Include all roles into the this schema. By including the superclass and all roles related to this class.



279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'app/models/virtual_class.rb', line 279

def load_attached_roles!
  return if @attached_roles_loaded

  super_kpath = kpath[0..-2]
  if super_kpath != ''
    include_role VirtualClass.find_by_kpath(super_kpath)
  end

  attached_roles.each do |role|
    include_role role
  end

  @attached_roles_loaded = true
end

#new_instance(hash = {}) ⇒ Object

Build new nodes instances of this VirtualClass



515
516
517
# File 'app/models/virtual_class.rb', line 515

def new_instance(hash={})
  real_class.new(hash, self)
end

#query_compilerObject

Return the SQLiss query compiler.



500
501
502
# File 'app/models/virtual_class.rb', line 500

def query_compiler
  real_class.query_compiler
end

#real_classObject



531
532
533
534
535
536
537
# File 'app/models/virtual_class.rb', line 531

def real_class
  @real_class ||= begin
    klass = Module::const_get(self[:real_class] || 'Node')
    raise NameError unless klass.ancestors.include?(Node)
    klass
  end
end

#real_class=(klass) ⇒ Object



539
540
541
# File 'app/models/virtual_class.rb', line 539

def real_class=(klass)
  @real_class = klass
end

#real_class?Boolean

Return true if the class reflects a real class (proxy for Ruby class).

Returns:

  • (Boolean)


356
357
358
# File 'app/models/virtual_class.rb', line 356

def real_class?
  @is_real_class
end

#safe_column_typesObject

Returns a hash of all column types that are RubyLess safe (declared as safe in a real class or just dynamic properties declared in the DB). In the Role: everything is safe (see VirtualClass#safe_column_types).



437
438
439
440
441
# File 'app/models/virtual_class.rb', line 437

def safe_column_types
  @safe_column_types ||= Hash[*safe_columns.map do |column|
    [column.name, RubyLess::SafeClass.safe_method_type_for_column(column, true)]
  end.flatten]
end

#safe_columnsObject

Return safe columns including super class’s safe columns. The columns are sorted by kpath, origin (VirtualClass first, Role next) and name.



426
427
428
429
430
431
432
# File 'app/models/virtual_class.rb', line 426

def safe_columns
  @safe_columns ||= begin
    (superclass.kind_of?(VirtualClass) ? superclass.safe_columns : []) +
    defined_safe_columns +
    attached_roles.map(&:defined_safe_columns).flatten.sort {|a,b| a.name <=> b.name}
  end
end

#safe_method_type(signature, receiver = nil) ⇒ Object

We use the VirtualClass to act as a proxy for the real class when resolving RubyLess methods. If the class reflects a ‘real’ class, only the methods explicitely declared as safe are safe. If the VirtualClass reflects a virtual class, all properties are considered safe.



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'app/models/virtual_class.rb', line 366

def safe_method_type(signature, receiver = nil)
  if type = real_class.safe_method_type(signature, receiver)
    return type
  elsif signature.size == 1
    method = signature.first
    if receiver && (query = receiver.opts[:query])
      if query.select_keys.include?(method)
        # Resolve by using information in the SELECT part
        # of the custom_query that found this node

        # In order to use types other then String, we use the overwritten property's
        # type.
        if type_name = query.types[method]
          klass = Node.cast_to_class(type_name)
          if klass == String
            return {:method => "attributes[#{method.inspect}]", :nil => true, :class => klass}
          elsif klass
            return {:method => "rcast(#{method.inspect}, #{type_name.inspect})", :nil => true, :class => klass}
          else
            return nil
          end
        elsif type = safe_column_types[method]
          return type.merge(:method => "attributes[#{method.inspect}]", :nil => true)
        elsif type = real_class.safe_method_type(signature)
          return type.merge(:nil => true)
        else
          return {:class => String, :method => "attributes[#{method.inspect}]", :nil => true}
        end
      end
    end

    if type = safe_column_types[method]
      type
    elsif method =~ /^(.+)_((id|zip|status|comment)(s?))\Z/ #&& !instance_methods.include?(method)
      return nil if $1 == 'cached_role'
      key = $3 == 'id' ? "zip#{$4}" : $2
      {:method => "rel[#{$1.inspect}].try(:other_#{key})", :nil => true, :class => ($4.blank? ? Number : [Number])}
    end
  else
    nil
  end
end

#sorted_rolesObject

List all roles ordered by ascending kpath and name



444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'app/models/virtual_class.rb', line 444

def sorted_roles
  @sorted_roles ||= begin
    res = []
    if superclass.kind_of?(VirtualClass)
      res << superclass.sorted_roles
    end
    res << self unless defined_safe_columns.empty?
    attached_roles.sort{|a,b| a.name <=> b.name}.each do |role|
      res << role unless role.defined_safe_columns.empty?
    end
    res.flatten!
    res
  end
end

#split_kpathObject



81
82
83
# File 'app/models/virtual_class.rb', line 81

def split_kpath
  @split_kpath ||= VirtualClass.split_kpath(kpath)
end

#sub_classesObject



268
269
270
# File 'app/models/virtual_class.rb', line 268

def sub_classes
  @sub_classes ||= VirtualClass.all_classes(self.kpath).sort {|a,b| a.name <=> b.name}
end

#superclassObject

Return virtual class’ super class or Node for the virtual class of Node.



461
462
463
464
465
466
467
# File 'app/models/virtual_class.rb', line 461

def superclass
  if kpath && kpath.size > 1
    VirtualClass.find_by_kpath(kpath[0..-2])
  else
    Node
  end
end

#to_sObject



294
295
296
# File 'app/models/virtual_class.rb', line 294

def to_s
  name
end

#zafu_new(hash = {}) ⇒ Object

Build new nodes instances of this VirtualClass from zafu.



520
521
522
# File 'app/models/virtual_class.rb', line 520

def zafu_new(hash={})
  real_class.new(Node.transform_attributes(hash.stringify_keys), self)
end