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.



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

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



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

def [](name)
  find_by_name(name)
end

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



240
241
242
# File 'app/models/virtual_class.rb', line 240

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



235
236
237
238
# File 'app/models/virtual_class.rb', line 235

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



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

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

.find_by_kpath(kpath) ⇒ Object



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

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

.find_by_name(name) ⇒ Object



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

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



501
502
503
504
505
506
507
508
509
510
511
512
# File 'app/models/virtual_class.rb', line 501

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



491
492
493
494
495
496
497
498
# File 'app/models/virtual_class.rb', line 491

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]



486
487
488
# File 'app/models/virtual_class.rb', line 486

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

#build_query(*args) ⇒ Object

Build SQLiss query.



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

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



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'app/models/virtual_class.rb', line 249

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



281
282
283
284
# File 'app/models/virtual_class.rb', line 281

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



541
542
543
544
545
# File 'app/models/virtual_class.rb', line 541

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

#defined_in_db?Boolean

Returns:

  • (Boolean)


418
419
420
# File 'app/models/virtual_class.rb', line 418

def defined_in_db?
  !id.blank?
end

#defined_safe_columnsObject

Return safe columns including super class’s safe columns



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'app/models/virtual_class.rb', line 423

def defined_safe_columns
  @defined_safe_columns ||= begin
    # If we have 
    list = defined_in_db? ? super : []
    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
      list += real_class.schema.defined_columns.values.select do |col|
        real_class.safe_method_type([col.name])
      end
    end
    list.sort {|a,b| a.name <=> b.name}
  end
end

#do_find(*args) ⇒ Object

Execute find



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

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

#exportObject



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
349
350
351
352
353
354
355
356
357
# File 'app/models/virtual_class.rb', line 307

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.



561
562
563
# File 'app/models/virtual_class.rb', line 561

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

#index_groupsObject

Cache index groups



566
567
568
# File 'app/models/virtual_class.rb', line 566

def index_groups
  @index_groups ||= super
end

#kpath_match?(kpath) ⇒ Boolean

check inheritance chain through kpath

Returns:

  • (Boolean)


360
361
362
# File 'app/models/virtual_class.rb', line 360

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.



288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'app/models/virtual_class.rb', line 288

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



531
532
533
# File 'app/models/virtual_class.rb', line 531

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

#query_compilerObject

Return the SQLiss query compiler.



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

def query_compiler
  real_class.query_compiler
end

#real_classObject



547
548
549
550
551
552
553
# File 'app/models/virtual_class.rb', line 547

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



555
556
557
# File 'app/models/virtual_class.rb', line 555

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)


365
366
367
# File 'app/models/virtual_class.rb', line 365

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).



453
454
455
456
457
# File 'app/models/virtual_class.rb', line 453

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.



442
443
444
445
446
447
448
# File 'app/models/virtual_class.rb', line 442

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.



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
408
409
410
411
412
413
414
415
416
# File 'app/models/virtual_class.rb', line 375

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



460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'app/models/virtual_class.rb', line 460

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



277
278
279
# File 'app/models/virtual_class.rb', line 277

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.



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

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

#to_sObject



303
304
305
# File 'app/models/virtual_class.rb', line 303

def to_s
  name
end

#zafu_new(hash = {}) ⇒ Object

Build new nodes instances of this VirtualClass from zafu.



536
537
538
# File 'app/models/virtual_class.rb', line 536

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