Class: Node

Overview

A Node is the root class of all elements in the zena application. Class inheritance diagram:

FIXME: some parts are not correct (Partial, Task, Request, Milestone). Either correct this tree or add these classes. Node (manages access and publication cycle)

|
+-- Page (web pages)
|     |
|     +--- Project (has it's own project_id. Can contain notes, collaborators, etc)
|     |
|     +--- Section (has it's own section_id = group of pages)
|            |
|            +--- Skin (theme: contains css, templates, etc)
|
+--- Document
|      |
|      +--- Image
|      |
|      +--- TextDocument       (for css, scripts)
|             |
|             +--- Partial     (uses the zafu templating language)
|                    |
|                    +--- Template  (entry for rendering)
|
+-- Note (date related information, event)
      |
      +--- Post (blog entry)

Properties

The Version class stores the node’s properties (attributes). You need to declare the attributes either in the virtual class or as a Role attached to an existing class in order to use them.

Attributes

Each node uses the following basic attributes:

Base attributes:

zip

unique id (incremented in each site’s scope).

_id

cached title (used to identify nodes in DB: not used in Zena)

site_id

site to which this node belongs to.

parent_id

parent node (every node except root is inserted in a unique place through this attribute).

user_id

creator of the node.

ref_lang

original node language.

created_at

creation date.

updated_at

modification date.

log_at

announcement date.

event_at

event date.

custom_base

boolean value. When set to true, the node’s url becomes it’s fullpath. All it descendants will use this node’s fullpath as their base url. See below for an example.

inherit

inheritance mode (0=custom, 1=inherit, -1=private).

Attributes inherited from the parent:

section_id

reference project (cannot be overwritten even if inheritance mode is custom).

rgroup_id

id of the readers group.

wgroup_id

id of the writers group.

dgroup_id

id of the publishers group.

skin_id

Skin to use when rendering the page (‘theme’).

Attributes used internally:

publish_from

earliest publication date from all published versions.

kpath

inheritance hierarchy. For example an Image has ‘NPDI’ (Node, Page, Document, Image), a Letter would have ‘NNTL’ (Node, Note, Task. Letter). This is used to optimize sql queries.

fullpath

cached full path made of ancestors’ zip (<gdparent zip>/<parent zip>/<self zip>).

basepath

cached base path (the base path is used to build the url depending on the ‘custom_base’ flag).

Node url

A node’s url is made of it’s class and zip. For the examples below, this is our site tree:

root
  |
  +--- projects (Page)
          |
          +--- worldTour (Project)
          |      |
          |      +--- photos (Page)
          |
          +--- music (Project)

The worldTour project’s url would look like:

/en/project21.html

The ‘photos’ url would be:

/en/page23.html

When custom base is set (only for descendants of Page), worldTour url becomes its fullpath:

/en/projects/worldTour

and the ‘photos’ url is now in the worldTour project’s basepath:

/en/projects/worldTour/page23.html

Setting ‘custom_base’ on a node should be done with caution as the node’s zip is on longer in the url and when you move the node around, there is no way to find the new location from the old url. Custom_base should therefore only be used for nodes that are not going to move.

Direct Known Subclasses

Document, Note, Page

Constant Summary collapse

VERSION_ATTRIBUTES =

List of version attributes that should be accessed as proxies ‘v_lang’, ‘v_status’, etc

%w{status lang publish_from backup}
Caster =
::ActiveRecord::ConnectionAdapters::Column
@@native_node_classes =
{'N' => self}
@@native_node_classes_by_name =
{'Node' => self}
@@unhandled_children =
[]

Constants included from Zena::Use::Workflow

Zena::Use::Workflow::WORKFLOW_ATTRIBUTES

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Zena::Use::Search::NodeClassMethods

match_query, search_index, search_records, search_text

Methods included from Zena::Acts::Secure

secure_scope, secure_write_scope, visitor=

Methods included from Zena::Acts::Secure::SecureResult

#construct_id_map, #secure_result

Methods included from Zena::Acts::SecureNode

acts_as_secure_node

Methods included from Zena::Use::Dates::ModelMethods

included

Methods included from Zena::Use::Dates::Common

#format_date

Methods included from Zena::Use::QueryNode::ModelMethods

#db_attr, #find, included, #safe_first, #start_node_zip

Methods included from Zena::Use::ScopeIndex::ModelMethods

included, #rebuild_index_with_scope_index!, #rebuild_scope_index!, #scope_index, scope_index_proc

Methods included from Zena::Use::Relations::ModelMethods

#add_link, included, #l_comment, #l_comment=, #l_date, #l_date=, #l_status, #l_status=, #link_id, #link_id=, #linked_node, #linked_node=, #rel, #rel=, #rel_attributes=, #relation_alias, #relation_links, #relation_proxy, #relation_proxy_from_link, #relations_for_form, #remove_link

Methods included from Zena::Use::Fulltext::ModelMethods

included, #rebuild_index_for_version_with_fulltext

Methods included from Zena::Use::PropEval::ModelMethods

included, #merge_prop_eval, #need_set__id, #rebuild_index_for_version_with_prop_eval, #set__id

Methods included from Zena::Use::VersionHash::ModelMethods

#rebuild_vhash, #v_public?, #version, #version_id, #vhash, #visible_versions

Methods included from Zena::Acts::Serializable::ModelMethods

#all_link_ids, #default_serialization_options, #export_ids, #export_properties, included, #to_xml

Methods included from Zena::Use::Ancestry::ModelMethods

#ancestors, #basepath, #fullpath_as_title, included, #is_ancestor?, #pseudo_id, #short_path, #z_ancestors

Methods included from Zena::Use::Workflow

#apply, #apply_with_callbacks, #auto_publish?, #can_apply?, #can_destroy_version?, #can_edit?, #can_propose?, #can_publish?, #can_refuse?, #can_remove?, #can_unpublish?, #can_update?, #destroy_version, #edit_content!, #get_publish_from, included, #propose, #publish, #redit, #refuse, #remove, #set_current_transition, #traductions, #transition_allowed?, #transition_for, #unpublish, #update_attributes, #would_change_original?

Methods included from Zena::Use::NestedAttributesAlias::ModelMethods

#attributes_with_nested_alias=, included, #nested_model_names_for_alias, #resolve_attributes_alias

Methods included from Zena::Acts::Enrollable::ModelMethods

#assigned_roles, #has_role?, included, #zafu_possible_roles

Methods included from Zena::Use::FieldIndex::ModelMethods

included, #property_field_index

Methods included from Zena::Use::MLIndex::ModelMethods

included, #index_reader, #rebuild_index_for_version, #rebuild_index_with_multi_lingual!

Methods included from Zena::Use::Kpath::InstanceMethods

included

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Zena::Use::Relations::ModelMethods

Class Method Details

.allowed_change_to_classesObject

List of classes that a node can change to.



463
464
465
# File 'app/models/node.rb', line 463

def allowed_change_to_classes
  change_to_classes_for_form.map {|k,v| v}
end

.author_procObject

Dynamic resolution of the author class from the user prototype



197
198
199
200
201
202
203
204
205
206
207
# File 'app/models/node.rb', line 197

def self.author_proc
  Proc.new do |h, r, s|
    res = {:method => 'author', :nil => true}
    if prototype = visitor.prototype
      res[:class] = prototype.vclass
    else
      res[:class] = VirtualClass['Node']
    end
    res
  end
end

.auto_create_discussionObject



992
993
994
# File 'app/models/node.rb', line 992

def auto_create_discussion
  false
end

.cast_to_class(type) ⇒ Object

Return class of cast value.



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'app/models/node.rb', line 302

def self.cast_to_class(type)
  case type
    when :string    then String
    when :text      then String
    when :integer   then Number
    when :float     then Number
    when :decimal   then Number
    when :datetime  then Time
    when :timestamp then Time
    when :time      then Time
    when :date      then Time
    when :binary    then String
    when :boolean   then Boolean
    else nil
  end
end

.change_to_classes_for_formObject

Class list to which this class can change to



458
459
460
# File 'app/models/node.rb', line 458

def change_to_classes_for_form
  classes_for_form(:class => 'Node', :without => 'Document')
end

.class_for_relation(rel) ⇒ Object

FIXME: Where is this used ?



874
875
876
877
878
879
880
881
882
883
884
885
# File 'app/models/node.rb', line 874

def class_for_relation(rel)
  case rel
  when 'author'
    User
  when 'traductions'
    Version
  when 'versions'
    Version
  else
    Node
  end
end

.classes_for_form(opts = {}) ⇒ Object

TODO: remove and use VirtualClass.classes_for_form directly



468
469
470
# File 'app/models/node.rb', line 468

def classes_for_form(opts={})
  VirtualClass[self.name].classes_for_form(opts)
end

.create_node(new_attributes, transform = true) ⇒ Object

TODO: cleanup and rename with something indicating the attrs cleanup that this method does.



653
654
655
656
657
658
659
# File 'app/models/node.rb', line 653

def create_node(new_attributes, transform = true)
  node = new_node(new_attributes, transform)
  if node.errors.empty?
    node.save
  end
  node
end

.create_nodes_from_folder(opts) ⇒ Object

Create new nodes from the data in a folder or archive.



662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
# File 'app/models/node.rb', line 662

def create_nodes_from_folder(opts)
  # TODO: all this needs refactoring (and moved into a module).
  # It's probably the messiest part of Zena.
  return [] unless (opts[:folder] || opts[:archive]) && (opts[:parent] || opts[:parent_id])
  scope = self.scoped_methods[0] || {}
  parent_id = opts[:parent_id] || opts[:parent][:id]
  folder    = opts[:folder]
  defaults  = (opts[:defaults] || {}).stringify_keys
  # Initial object class
  klass     = opts[:class] || opts[:klass] || "Page"
  res       = {}

  unless folder
    # Create from archive
    res = nil
    extract_archive(opts[:archive]) do |folder|
      res = create_nodes_from_folder(:folder => folder, :parent_id => parent_id, :defaults => defaults, :klass => klass)
    end

    return res
  end

  entries = Dir.entries(folder).reject { |f| f =~ /^[\.~]|^__/ }.map do |filename|
    [filename, String.from_filename(filename)]
  end.sort

  index  = 0

  while entries[index]
    catch (:next_entry) do
      type = current_obj = sub_folder = document_path = nil
      versions = []
      filename, title = *entries[index]

      path     = File.join(folder, filename)

      if File.stat(path).directory?
        type       = :folder
        sub_folder = path
        attrs      = defaults.dup
        attrs['v_lang'] ||= visitor.lang
      elsif title =~ /^(.+?)(\.\w\w|)(\.\d+|)\.zml$/  # bird.jpg.en.zml
        # node content in yaml
        type      = :node
        title     = "#{$1}#{$4}"
        lang      = $2.blank? ? nil : $2[1..-1]

        # no need for base_node (this is done after all with parse_assets in the controller)
        attrs  = defaults.merge(get_attributes_from_yaml(path))
        attrs['title'] = title
        attrs['v_lang']    = lang || attrs['v_lang'] || visitor.lang
        versions << attrs
      elsif title =~ /^((.+?)\.(.+?))(\.\w\w|)(\.\d+|)$/ # bird.jpg.en
        type      = :document
        title     = $1
        attrs     = defaults.dup
        lang      = $4.blank? ? nil : $4[1..-1]
        attrs['v_lang'] = lang || attrs['v_lang'] || visitor.lang
        attrs['ext']  = $3
        document_path = path
      else
        # Document without extension
        type      = :document
        attrs     = defaults.dup
        lang      = nil
        attrs['v_lang'] = lang || attrs['v_lang'] || visitor.lang
        attrs['ext']  = 'bin'
        document_path = path
      end

      index += 1
      while entries[index] && entries[index][1] =~ /^#{title}(\.\w\w|)(\.\d+|)\.zml$/ # bird.jpg.en.zml
        lang   = $1.blank? ? visitor.lang : $1[1..-1]
        path   = File.join(folder,entries[index][0])

        # we have a zml file. Create a version with this file
        # no need for base_node (this is done after all with parse_assets in the controller)
        attrs = defaults.merge(get_attributes_from_yaml(path))
        attrs['title']  ||= title
        attrs['v_lang'] ||= lang
        versions << attrs

        index += 1
      end

      if versions.empty?
        if type == :folder
          # minimal node for a folder
          attrs['title']    = title
          attrs['v_lang'] ||= lang
          attrs['class']    = klass
          versions << attrs
        elsif type == :document
          # minimal node for a document
          attrs['title']    = title
          attrs['v_lang'] ||= lang
          versions << attrs
        end
      end

      new_object = false
      versions.each do |attrs|
        # FIXME: same lang: remove before update current_obj.remove if current_obj.v_lang == attrs['v_lang'] && current_obj.v_status != Zena::Status::Red
        # FIXME: current_obj.publish if attrs['v_status'].to_i == Zena::Status::Pub
        if type == :document
          attrs['title' ] = attrs['title'].split('.')[0..-2].join('.')
          if document_path
            attrs['ext'] ||= document_path.split('.').last
            # file
            insert_zafu_headings = false
            if opts[:parent_class] == 'Skin'
              if ['html','xhtml'].include?(attrs['ext']) && attrs['title'] == 'index'
                attrs['ext']   = 'zafu'
                attrs['title'] = 'Node'
                attrs['content_type'] = 'text/zafu'
                insert_zafu_headings = true
              elsif attrs['ext'] == 'yml' && attrs['title'] == '_roles'
                # import roles
                # FIXME: security. We should show diff and ask for validation...
                ::Role.import(YAML.load(File.read(document_path)))
                # Continue in outer loop.
                throw :next_entry
              end
            end

            ctype = Zena::EXT_TO_TYPE[attrs['ext']]
            ctype = ctype ? ctype[0] : "application/octet-stream"
            attrs['content_type'] = ctype


            File.open(document_path) do |f|
              file = uploaded_file(f, filename, ctype)
              (class << file; self; end;).class_eval do
                alias o_read read
                define_method(:read) do
                  if insert_zafu_headings
                    o_read.sub(%r{</head>},"  <r:stylesheets/>\n  <r:javascripts/>\n  <r:uses_datebox/>\n</head>")
                  else
                    o_read
                  end
                end
              end
              current_obj = create_or_update_node(attrs.merge(:file => file, :klass => 'Document', :_parent_id => parent_id))
            end
            document_path = nil
          else
            current_obj = create_or_update_node(attrs.merge(:_parent_id => parent_id, :klass => 'Document'))
          end
        else
          # :folder, :node
          current_obj = create_or_update_node(attrs.merge(:_parent_id => parent_id))
        end
        new_object = new_object || current_obj.instance_variable_get(:@new_record_before_save)
      end
      current_obj.instance_variable_set(:@new_record_before_save, new_object)
      current_obj.instance_variable_set(:@versions_count, versions.size)

      res[current_obj[:id].to_i] = current_obj

      res.merge!(create_nodes_from_folder(:folder => sub_folder, :parent_id => current_obj[:id], :defaults => defaults, :parent_class => current_obj.klass)) if sub_folder && !current_obj.new_record?
    end # catch :next_entry
  end
  res
end

.create_or_update_node(new_attributes) ⇒ Object

def attr_public?(attribute)

if attribute.to_s =~ /(.*)_zips?$/
  return true if self.ancestors.include?(Node) && RelationProxy.find_by_role($1.singularize)
end
super

end



568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
# File 'app/models/node.rb', line 568

def create_or_update_node(new_attributes)
  attributes = transform_attributes(new_attributes)

  v_lang = attributes['v_lang']
  if !current_site.lang_list.include?(v_lang)
    attributes['v_lang'] = current_site.lang_list.first
  end

  if zip = attributes.delete('parent_zip')
    if id = secure(Node) { Node.translate_pseudo_id(zip, :id, self) }
      attributes['parent_id'] = id
    else
      node = Node.new
      node.errors.add('parent_id', 'could not be found')
      return node
    end
  end

  unless attributes['title'] && attributes['parent_id']
    node = Node.new
    node.errors.add('title', "can't be blank")     if attributes['title'].blank?
    node.errors.add('parent_id', "can't be blank") if attributes['parent_id'].blank?
    return node
  end

  # FIXME: remove 'with_exclusive_scope' once scopes are clarified and removed from 'secure'
  node = Node.send(:with_exclusive_scope) do
    find_by_parent_title_and_kpath(attributes['parent_id'], attributes['title'], nil)
  end

  if node
    visitor.visit(node) # secure
    # TODO: class ignored (could be used to transform from one class to another...)
    attributes.delete('class')
    attributes.delete('klass')
    updated_date = node.updated_at
    node.update_attributes(attributes)

    if updated_date != node.updated_at
      node[:create_or_update] = 'updated'
    else
      node[:create_or_update] = 'same'
    end
  else
    node = create_node(attributes, false)
    node[:create_or_update] = 'new'
  end

  node
end

.extract_archive(archive) ⇒ Object



827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
# File 'app/models/node.rb', line 827

def extract_archive(archive)
  begin
    n = 0
    # TODO: we could move the tmp folder inside sites/{current_site}/tmp
    folder = File.join(RAILS_ROOT, 'tmp', sprintf('%s.%d.%d', 'import', $$, n))
  end while File.exists?(folder)

  begin
    FileUtils::mkpath(folder)

    if archive.kind_of?(StringIO)
      filename = archive.original_filename
      tempf = Tempfile.new(archive.original_filename)
      File.open(tempf.path, 'wb') { |f| f.syswrite(archive.read) }
      archive = tempf
    else
      filename = archive.original_filename
    end

    # extract file in this temporary folder.
    # FIXME: SECURITY is there a security risk here ?
    # FIXME: not compatible with Windows.
    if filename =~ /\.tgz$/
      `tar -C '#{folder}' -xz < '#{archive.path}'`
    elsif filename =~ /\.tar$/
      `tar -C '#{folder}' -x < '#{archive.path}'`
    elsif filename =~ /\.zip$/
      `unzip -d '#{folder}' '#{archive.path}'`
    elsif filename =~ /(.*)(\.gz|\.z)$/
      `gzip -d '#{archive.path}' -c > '#{folder}/#{$1.gsub("'",'')}'`
    else
      # FIXME: send errors back
      puts "BAD #{archive.inspect}"
    end
    yield folder
  ensure
    FileUtils::rmtree(folder)
  end
end

.find_by_parent_title_and_kpath(parent_id, title, kpath = nil, opts = {}) ⇒ Object



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'app/models/node.rb', line 390

def find_by_parent_title_and_kpath(parent_id, title, kpath = nil, opts = {})
  if cond = opts[:conditions]
    cond[0] = Array(cond[0])
  else
    cond = opts[:conditions] = [[]]
  end

  if kpath
    cond[0] << "kpath like ?"
    cond << "#{kpath}%"
  end
  cond[0] << "site_id = ? AND parent_id = ?"
  cond << current_site.id << parent_id

  find_by_title(title, opts)
end

.find_by_title(title, opts = {}) ⇒ Object

Find node by the indexed title.



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'app/models/node.rb', line 408

def find_by_title(title, opts = {})
  if cond = opts[:conditions]
    cond[0] = Array(cond[0])
  else
    cond = opts[:conditions] = [[]]
  end

  if opts.delete(:like)
    cond[0] << "id1.value LIKE ?"
  else
    cond[0] << "id1.value = ?"
  end
  cond << title

  cond[0] = cond[0].join(' AND ')

  opts[:joins] = Node.title_join
  opts[:select] = 'nodes.*'

  Node.find(:first, opts)
end

.find_by_zip(zip) ⇒ Object

Raises:

  • (ActiveRecord::RecordNotFound)


867
868
869
870
871
# File 'app/models/node.rb', line 867

def find_by_zip(zip)
  node = find(:first, :conditions=>"zip = #{zip.to_i}")
  raise ActiveRecord::RecordNotFound unless node
  node
end

.find_node_by_pseudo(id, base_node = nil) ⇒ Object

Find a node based on a query shortcut. Used by zazen to create a link for “”::art for example.



518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'app/models/node.rb', line 518

def find_node_by_pseudo(id, base_node = nil)
  raise Zena::AccessViolation if self.scoped_methods == []
  str = id.to_s
  if str =~ /\A\d+\Z/
    # zip
    find_by_zip(str)
  elsif path = str[/\A\(([^\)]+)\)\Z/,1]
    if path[0..0] == '/'
      path = path[1..-1].split('/').map {|p| String.from_filename(p) }
      find_by_path(path)
    elsif base_node
      # transform ../../foo and 45/32/61/72 ==> 'foo' and 45/32
      path = path.split('/')
      root = base_node.fullpath.split('/')
      while path[0] == '..'
        root.pop
        path.shift
      end

      path = path.map {|p| String.from_filename(p) }

      if base_node.zip == root.last.to_i
        find_by_path(path, base_node.id)
      elsif root.last
        if base = find_by_zip(root.last)
          find_by_path(path, base.id)
        else
          nil
        end
      else
        find_by_path(path)
      end
    else
      # do not use (path) pseudo when there is no base_node (during create_or_update_node for example).
      # FIXME: path pseudo is needed for links... and it should be done here (egg and hen problem)
      nil
    end
  elsif str =~ /\A:?([^\+]+)(\+*)\Z/
    offset = $2.to_s.size
    Node.search_records($1.gsub('-',' '), :offset => offset, :limit => 1).first
  end
end

.get_attributes_from_yaml(filepath, base_node = nil) ⇒ Object



972
973
974
975
976
# File 'app/models/node.rb', line 972

def get_attributes_from_yaml(filepath, base_node = nil)
  attributes = YAML::load( File.read( filepath ) )
  attributes.delete(:_parent_id)
  transform_attributes(attributes, base_node)
end

.get_class(rel, opts = {}) ⇒ Object

Return class or virtual class from name. FIXME: remove once everything can use VirtualClass



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

def get_class(rel, opts={})
  # mushroom_types ==> MushroomType
  class_name = rel =~ /\A[a-z]/ ? rel.singularize.camelize : rel
  vclass = VirtualClass[class_name]
  if vclass && opts[:create] && vclass.id
    # TODO: how do we deal with real class ? (Currently = pass).
    visitor.group_ids.include?(vclass.create_group_id) ? vclass : nil
  else
    vclass
  end
end

.get_role(rel) ⇒ Object

Find a role by name.



497
498
499
500
501
# File 'app/models/node.rb', line 497

def get_role(rel)
  # mushroom_types ==> MushroomType
  role_name = rel =~ /\A[a-z]/ ? rel.singularize.camelize : rel
  Role.first(:conditions => ['name = ? AND site_id = ?', role_name, current_site.id])
end

.inherited(child) ⇒ Object



382
383
384
385
386
387
388
# File 'app/models/node.rb', line 382

def inherited(child)
  super
  unless child.name.blank?
    # Do not register anonymous classes created during Zafu compilation
    @@unhandled_children << child
  end
end

.inspectObject

Seeing all the columns of the Node class on every inspect does not help at all.



988
989
990
# File 'app/models/node.rb', line 988

def inspect
  to_s
end

.kpath_match?(kpath) ⇒ Boolean

check inheritance chain through kpath

Returns:

  • (Boolean)


453
454
455
# File 'app/models/node.rb', line 453

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

.kpaths_for_form(opts = {}) ⇒ Object

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



474
475
476
477
478
479
480
# File 'app/models/node.rb', line 474

def kpaths_for_form(opts={})
  VirtualClass.all_classes(opts).map do |vclass|
    # white spaces are insecable spaces (not ' ')
    a, b = vclass.kpath, vclass.name
    [a[1..-1].gsub(/./,'  ') + b, a]
  end
end

.load_unhandled_childrenObject



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

def load_unhandled_children
  # this is to make sure subclasses are loaded before the first call
  # TODO: find a better way to make sure they are all loaded
  [Note,Page,Project,Section,Document,Image,TextDocument,Skin,Template]
  while child = @@unhandled_children.pop
    @@native_node_classes[child.kpath] = child
    @@native_node_classes_by_name[child.name] = child
  end
end

.make_schemaObject

We want to use a Role as schema for properties defined in the real_class instead of Property::Schema.



127
128
129
130
131
132
133
134
135
# File 'app/models/node.rb', line 127

def self.make_schema
  ::Role.new(:name => name).tap do |role|
    role.kpath = self.kpath
    # Enable property method definitions.
    role.klass      = self
    # Used for property inheritance.
    role.real_class = self
  end
end

.native_classesObject

Return the list of (kpath,subclasses) for the current class.



431
432
433
434
# File 'app/models/node.rb', line 431

def native_classes
  load_unhandled_children
  @@native_node_classes
end

.native_classes_by_nameObject

Return the list of (name,class) for the current class.



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

def native_classes_by_name
  load_unhandled_children
  @@native_node_classes_by_name
end

.new(hash = {}, vclass = nil) ⇒ Object Also known as: new_instance



368
369
370
371
372
373
374
# File 'app/models/node.rb', line 368

def new(hash={}, vclass = nil)
  node = super()
  # set virtual_class (acts as schema) before setting attributes
  node.virtual_class = vclass || VirtualClass[self.name]
  node.attributes = hash
  node
end

.new_node(new_attributes, transform = true) ⇒ Object

TODO: cleanup and rename with something indicating the attrs cleanup that this method does.



620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
# File 'app/models/node.rb', line 620

def new_node(new_attributes, transform = true)
  attributes = transform ? transform_attributes(new_attributes) : new_attributes

  klass_name = attributes.delete('class') || attributes.delete('klass')
  klass_name ||= attributes['file'] ? 'Document' : 'Page'
  
  if klass_name.kind_of?(VirtualClass) || klass_name.kind_of?(Class)
    klass = klass_name
  else
    unless klass = get_class(klass_name, :create => true)
      node = Node.new
      node.instance_eval { @attributes.merge!(attributes) }
      node.errors.add('klass', 'invalid')
      # This is to show the klass in the form seizure
      node.instance_variable_set(:@klass, klass_name.to_s)
      def node.klass; @klass; end
      return node
    end
  end
  
  if attributes['file'] && !(klass.kpath =~ %r{^ND})
    klass = VirtualClass['Document']
  end

  if klass.kind_of?(VirtualClass)
    node = secure(klass.real_class) { klass.new_instance(attributes) }
  else
    node = secure(klass) { klass.new_instance(attributes) }
  end
  node
end

.plural_relation?(rel) ⇒ Boolean

Returns:

  • (Boolean)


887
888
889
890
891
892
893
894
895
896
897
898
# File 'app/models/node.rb', line 887

def plural_relation?(rel)
  rel = rel.split(/\s/).first
  if ['root', 'parent', 'self', 'children', 'documents_only', 'all_pages'].include?(rel) || Node.get_class(rel)
    rel.pluralize == rel
  elsif rel =~ /\A\d+\Z/
    false
  else
    relation = RelationProxy.find_by_role(rel.singularize)
    return rel =~ /s$/ unless relation
    relation.target_role == rel.singularize ? !relation.target_unique : !relation.source_unique
  end
end

.transform_attributes(new_attributes, base_node = nil, change_timezone = true, is_link = false) ⇒ Object

Translate attributes from the visitor’s reference to the application. This method translates dates, zazen shortcuts and zips and returns a stringified hash.



902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
# File 'app/models/node.rb', line 902

def transform_attributes(new_attributes, base_node = nil, change_timezone = true, is_link = false)
  res = {}
  res['parent_id'] = new_attributes[:_parent_id] if new_attributes[:_parent_id] # real id set inside zena.

  copy_node = new_attributes.delete(:_copy)
  attributes = new_attributes.stringify_keys
  
  if copy_node || attributes['copy_id']
    if !copy_node && attributes['copy_id'].blank?
      attributes = Node.new.replace_attributes_in_values(attributes)
    else
      copy_node ||= Node.find_by_zip(attributes.delete('copy_id'))
      attributes = copy_node.replace_attributes_in_values(attributes)
    end
  end

  if !res['parent_id'] && p = attributes['parent_id']
    res['parent_zip'] = p unless p.blank?
  end

  attributes.each do |key, value|
    next if ['parent_id', 'parent_zip', '_parent_id'].include?(key)

    if %w{rgroup_id wgroup_id dgroup_id}.include?(key)
      res[key] = Group.translate_pseudo_id(value, :id) || value
    elsif %w{rgroup wgroup dgroup}.include?(key)
      res["#{key}_id"] = Group.translate_pseudo_id(value, :id) || value
    elsif %w{user_id}.include?(key)
      res[key] = User.translate_pseudo_id(value, :id) || value
    elsif %w{link_id}.include?(key)
      # Link id, not translated
      res[key] = value
    elsif %w{id create_at updated_at}.include?(key)
      # ignore (can be present in xml)
    elsif %w{log_at event_at v_publish_from}.include?(key) || (is_link && %w{date}.include?(key))
      # FIXME: !!! We need to fix timezone parsing in dates depending on the Schema used. This means
      # that we probably need to do this at the property level (during write).
      if value.kind_of?(Time)
        res[key] = value
      elsif value
        # parse date
        if key == 'date'
          # TODO: this is a temporary hack because date in links do not support timezones/formats properly
          res[key] = value.to_utc("%Y-%m-%d %H:%M:%S")
        else
          res[key] = value.to_utc(_(Zena::Use::Dates::DATETIME), change_timezone ? visitor.tz : nil)
        end
      end
    elsif key =~ /^(\w+)_id$/
      res["#{$1}_zip"] = value
    elsif key =~ /^(\w+)_ids$/
      res["#{$1}_zips"] = value.kind_of?(Array) ? value : value.split(',').map(&:strip)
    elsif key == 'v_status' || key == 'file'
      res[key] = value unless value.blank?
    elsif value.kind_of?(Hash)
      res[key] = transform_attributes(value, base_node, change_timezone, %w{link rel rel_attributes}.include?(key) || is_link)
    else
      # translate zazen
      if value.kind_of?(String)
        # FIXME: ignore if 'text' of a TextDocument...
        res[key] = ZazenParser.new(value,:helper=>self).render(:translate_ids=>:zip, :node=>base_node)
      else
        res[key] = value
      end
    end
  end

  res
end

.translate_pseudo_id(id, sym = :id, base_node = nil) ⇒ Object

Find a node’s attribute based on a pseudo (id or path). Used by zazen to create a link for “”::art or “”:(people/ant) for example.



504
505
506
507
508
509
510
511
512
513
514
515
# File 'app/models/node.rb', line 504

def translate_pseudo_id(id, sym = :id, base_node = nil)
  if id.to_s =~ /\A(-?)(\d+)\Z/
    # zip
    # FIXME: this is not secure
    res = Zena::Db.fetch_attribute("SELECT #{sym} FROM nodes WHERE site_id = #{current_site[:id]} AND zip = '#{$2}'")
    res ? ($1.blank? ? res.to_i : -res.to_i) : nil
  elsif node = find_node_by_pseudo(id,base_node)
    node[sym]
  else
    nil
  end
end

.zafu_attribute(node, attribute) ⇒ Object

Return a safe string to access node attributes in compiled templates and compiled sql.



979
980
981
982
983
984
985
# File 'app/models/node.rb', line 979

def zafu_attribute(node, attribute)
  if node.kind_of?(String)
    raise Exception.new("You should use safe_method_type...")
  else
    node.safe_read(attribute)
  end
end

Instance Method Details

#all_relationsObject

Used by zafu to find the search score def score

self[:score]

end



1124
1125
1126
# File 'app/models/node.rb', line 1124

def all_relations
  @all_relations ||= self.vclass.all_relations(self)
end

#archiveObject

create a ‘tgz’ archive with node content and children, returning temporary file path



1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
# File 'app/models/node.rb', line 1382

def archive
  n = 0
  while true
    folder_path = File.join(RAILS_ROOT, 'tmp', sprintf('%s.%d.%d', 'archive', $$, n))
    n += 1
    break unless File.exists?(folder_path)
  end

  begin
    FileUtils::mkpath(folder_path)
    export_to_folder(folder_path)
    tempf = Tempfile.new(title.to_filename)
    `cd #{folder_path.inspect}; tar czf #{tempf.path.inspect} *`
  ensure
    FileUtils::rmtree(folder_path)
  end
  tempf
end

#asset_path(asset_filename) ⇒ Object

Return save path for an asset (element produced by text like a png file from LateX)



1096
1097
1098
1099
1100
# File 'app/models/node.rb', line 1096

def asset_path(asset_filename)
  # It would be nice to move this outside 'self[:id]' so that the same asset can
  # be used by many pages... But then, how do we expire unused assets ?
  "#{SITES_ROOT}#{site.data_path}/asset/#{self[:id]}/#{asset_filename}"
end

#authorObject

ACCESSORS



1199
1200
1201
# File 'app/models/node.rb', line 1199

def author
  user.node
end

#can_auto_create_discussion?Boolean

Automatically create a discussion if any of the following conditions are met:

  • there already exists an outside, open discussion for another language

  • the node is not published (creates an internal discussion)

  • the user has drive access to the node

Returns:

  • (Boolean)


1301
1302
1303
1304
1305
1306
# File 'app/models/node.rb', line 1301

def can_auto_create_discussion?
  can_drive? ||
  (v_status != Zena::Status::Pub) ||
  Discussion.find(:first, :conditions=>[ "node_id = ? AND inside = ? AND open = ?",
                           self[:id], false, true ])
end

#can_comment?Boolean

Return true if it is allowed to add comments to the node in the current context

Returns:

  • (Boolean)


1349
1350
1351
# File 'app/models/node.rb', line 1349

def can_comment?
  visitor.commentator? && discussion && discussion.open?
end

#commentsObject

Comments for the current context. Returns nil when there is no discussion.



1330
1331
1332
1333
1334
1335
1336
1337
# File 'app/models/node.rb', line 1330

def comments
  if discussion
    res = discussion.comments(:with_prop=>can_drive?)
    res == [] ? nil : res
  else
    nil
  end
end

#comments_countObject

TODO: remove, replace by relation proxy: proxy.count…



1340
1341
1342
1343
1344
1345
1346
# File 'app/models/node.rb', line 1340

def comments_count
  if discussion
    discussion.comments_count(:with_prop=>can_drive?)
  else
    0
  end
end

#content_langObject

Return the code language used for syntax highlighting.



1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
# File 'app/models/node.rb', line 1103

def content_lang
  ctype = prop['content_type']
  if ctype =~ /^text\/(.*)/
    case $1
    when 'x-ruby-script'
      'ruby'
    when 'html', 'zafu'
      'zafu'
    else
      $1
    end
  else
    nil
  end
end

#dataObject

Find all data entries linked to the current node



1215
1216
1217
1218
# File 'app/models/node.rb', line 1215

def data
  list = DataEntry.find(:all, :conditions => "node_a_id = #{id} OR node_b_id = #{id} OR node_c_id = #{id} OR node_d_id = #{id}", :order => 'date ASC,created_at ASC')
  list == [] ? nil : list
end

#discussionObject

Find the discussion for the current context (v_status and v_lang). This automatically creates a new #Discussion if there is no closed or open discussion for the current lang and Node#can_auto_create_discussion? is true



1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
# File 'app/models/node.rb', line 1285

def discussion
  return @discussion if defined?(@discussion)

  @discussion = Discussion.find(:first, :conditions=>[ "node_id = ? AND inside = ? AND lang = ?",
    self[:id], v_status != Zena::Status::Pub, v_lang ], :order=>'id DESC') ||
    if can_auto_create_discussion?
      Discussion.new(:node_id=>self[:id], :lang=>v_lang, :inside=>(v_status != Zena::Status::Pub))
    else
      nil
    end
end

#dyn_attribute_keysObject



1054
1055
1056
# File 'app/models/node.rb', line 1054

def dyn_attribute_keys
  (version.dyn.keys + (virtual_class ? virtual_class.dyn_keys.to_s.split(',').map(&:strip) : [])).uniq.sort
end

#empty?Boolean

Include data entry verification in multiversion’s empty? method.

Returns:

  • (Boolean)


1376
1377
1378
1379
# File 'app/models/node.rb', line 1376

def empty?
  return true if new_record?
  super && 0 == self.class.count_by_sql("SELECT COUNT(*) FROM #{DataEntry.table_name} WHERE node_a_id = #{id} OR node_b_id = #{id} OR node_c_id = #{id} OR node_d_id = #{id}")
end

#export_keysObject

List of attribute keys to export in a zml file.



1450
1451
1452
1453
1454
1455
# File 'app/models/node.rb', line 1450

def export_keys
  {
    :zazen => Hash[*prop.select { |k, v| v.kind_of?(String) }.flatten],
    :dates => Hash[*prop.select { |k, v| v.kind_of?(Time) }.flatten],
  }
end

#export_to_folder(path) ⇒ Object

export node content and children into a folder



1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
# File 'app/models/node.rb', line 1402

def export_to_folder(path)
  children = secure(Node) { Node.find(:all, :conditions=>['parent_id = ?', self[:id] ]) }

  if kind_of?(Document) && (kind_of?(TextDocument) || text.blank? || text == "!#{zip}!")
    # skip zml
    # TODO: this should better check that version content is really useless
  elsif text.blank? && klass == 'Page' && children
    # skip zml
  else
    File.open(File.join(path, title.to_filename + '.zml'), 'wb') do |f|
      f.puts self.to_yaml
    end
  end

  if kind_of?(Document)
    data = kind_of?(TextDocument) ? StringIO.new(text) : file
    File.open(File.join(path, filename), 'wb') { |f| f.syswrite(data.read) }
  end

  if children
    content_folder = File.join(path, title.to_filename)
    if !FileUtils::mkpath(content_folder)
      puts "Problem..."
    end
    children.each do |child|
      child.export_to_folder(content_folder)
    end
  end
end

#find_node_by_pseudo(string, base_node = nil) ⇒ Object

This is needed during ‘unparse_assets’ when the node is it’s own helper



1463
1464
1465
# File 'app/models/node.rb', line 1463

def find_node_by_pseudo(string, base_node = nil)
  secure(Node) { Node.find_node_by_pseudo(string, base_node || self) }
end

#get_discussion_idObject

Return current discussion id (used by query_builder)



1235
1236
1237
# File 'app/models/node.rb', line 1235

def get_discussion_id
  (discussion && !discussion.new_record?) ? discussion[:id] : '0'
end

#get_project_idObject

Return self if the node is a kind of Project. Return project_id otherwise.



1246
1247
1248
1249
# File 'app/models/node.rb', line 1246

def get_project_id
  # root node is it's own section and project
  self[:parent_id].nil? ? self[:id] : self[:project_id]
end

#get_section_idObject

Return self if the node is a kind of Section. Return section_id otherwise.



1240
1241
1242
1243
# File 'app/models/node.rb', line 1240

def get_section_id
  # root node is it's own section and project
  self[:parent_id].nil? ? self[:id] : self[:section_id]
end

#klassObject



1050
1051
1052
# File 'app/models/node.rb', line 1050

def klass
  @new_klass || @set_klass || vclass.to_s
end

#klass=(str) ⇒ Object



1058
1059
1060
1061
# File 'app/models/node.rb', line 1058

def klass=(str)
  return if str == klass
  @new_klass = str
end

#klass_changed?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'app/models/node.rb', line 116

def klass_changed?
  kpath_changed?
end

#kpath_match?(kpath) ⇒ Boolean

check inheritance chain through kpath

Returns:

  • (Boolean)


1039
1040
1041
# File 'app/models/node.rb', line 1039

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

#login_infoObject



1210
1211
1212
# File 'app/models/node.rb', line 1210

def 
  secure(User) { User.find(:first, :conditions => {:node_id => self.id})}
end

#m_authorObject



1312
# File 'app/models/node.rb', line 1312

def m_author; ''; end

#m_author=(str) ⇒ Object



1324
1325
1326
1327
# File 'app/models/node.rb', line 1324

def m_author=(str)
  @add_comment ||= {}
  @add_comment[:author] = str
end

#m_textObject

FIXME: use nested_attributes_alias and try to use native Rails to create the comment comment_attributes=, …



1310
# File 'app/models/node.rb', line 1310

def m_text; ''; end

#m_text=(str) ⇒ Object



1314
1315
1316
1317
# File 'app/models/node.rb', line 1314

def m_text=(str)
  @add_comment ||= {}
  @add_comment[:text] = str
end

#m_titleObject



1311
# File 'app/models/node.rb', line 1311

def m_title; ''; end

#m_title=(str) ⇒ Object



1319
1320
1321
1322
# File 'app/models/node.rb', line 1319

def m_title=(str)
  @add_comment ||= {}
  @add_comment[:title] = str
end

#merge_multi_errors(key, object) ⇒ Object

This is an adaptation of Versions::Multi code to use our special v_ shortcut to access version attributes.



345
346
347
348
349
350
351
# File 'app/models/node.rb', line 345

def merge_multi_errors(key, object)
  if key == 'version'
    super('v', object)
  else
    super
  end
end

#new_child(opts = {}, transform = true) ⇒ Object

Create a child and let him inherit from rwp groups and section_id



1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
# File 'app/models/node.rb', line 1181

def new_child(opts={}, transform = true)
  c = Node.new_node(opts, transform)
  c.parent_id  = self[:id]
  c.instance_variable_set(:@parent, self)

  c.visitor    = visitor

  c.inherit = 1
  c.rgroup_id  = self.rgroup_id
  c.wgroup_id  = self.wgroup_id
  c.dgroup_id  = self.dgroup_id

  c.section_id = self.get_section_id
  c.project_id = self.get_project_id
  c
end

#o_skinObject



1175
# File 'app/models/node.rb', line 1175

alias o_skin skin

#o_userObject



1203
# File 'app/models/node.rb', line 1203

alias o_user user

#parent(is_secure = true) ⇒ Object

Find parent



1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
# File 'app/models/node.rb', line 1129

def parent(is_secure = true)
  # make sure the cache is in sync with 'parent_id' (used during validation)
  if self[:parent_id].nil?
    nil
  elsif is_secure
    # cache parent result (done through secure query)
    return @parent if @parent && @parent[:id] == self[:parent_id]
    @parent = secure(Node) { Node.find(self[:parent_id]) }
  else
    # not secured (inside an exclusive scope)
    return @parent_insecure if @parent_insecure && @parent_insecure[:id] == self[:parent_id]
    @parent_insecure = secure(Node, :secure => false) { Node.find(self[:parent_id]) }
  end
end

#parent_zipObject

Id to zip mapping for parent_id. Used by zafu and forms.



1252
1253
1254
# File 'app/models/node.rb', line 1252

def parent_zip
  @parent_zip || parent.try(:zip)
end

#parent_zip=(zip) ⇒ Object

When setting parent trough controllers, we receive parent_zip=.



1257
1258
1259
# File 'app/models/node.rb', line 1257

def parent_zip=(zip)
  @parent_zip = zip
end

#parse_assets(text, helper, key) ⇒ Object

Parse text content and replace all relative urls (‘../projects/art’) by ids (‘34’)



1084
1085
1086
1087
# File 'app/models/node.rb', line 1084

def parse_assets(text, helper, key)
  # helper is used in textdocuments
  ZazenParser.new(text,:helper=>helper).render(:translate_ids => :zip, :node => self)
end

#parse_keysObject

List of attribute keys to transform (change references, etc).



1458
1459
1460
# File 'app/models/node.rb', line 1458

def parse_keys
  export_keys[:zazen].keys
end

#projectObject

Return self if the current node is a project else find project.



1161
1162
1163
# File 'app/models/node.rb', line 1161

def project
  self.kind_of?(Project) ? self : real_project
end

#project_zipObject

Id to zip mapping for project_id. Used by zafu and forms.



1276
1277
1278
# File 'app/models/node.rb', line 1276

def project_zip
  project[:zip]
end

#rcast(key, type) ⇒ Object

Read with cast to an appropriate instance. This is used along with custom select in QueryBuilder queries.



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'app/models/node.rb', line 321

def rcast(key, type)
  @rcast_cache ||= {}
  @rcast_cache[key] ||= begin
    value = @attributes[key]
    return nil if value.nil?
    case type
      when :string    then value
      when :text      then value
      when :integer   then value.to_i rescue value ? 1 : 0
      when :float     then value.to_f
      when :decimal   then Caster.value_to_decimal(value)
      when :datetime  then Caster.string_to_time(value)
      when :timestamp then Caster.string_to_time(value)
      when :time      then Caster.string_to_time(value)
      when :date      then Caster.string_to_time(value)
      when :binary    then Caster.binary_to_string(value)
      when :boolean   then Caster.value_to_boolean(value)
      else value
    end
  end
end

#real_project(is_secure = true) ⇒ Object

Find real project (Project’s project if node is a Project)



1166
1167
1168
1169
1170
1171
1172
1173
# File 'app/models/node.rb', line 1166

def real_project(is_secure = true)
  return self if self[:parent_id].nil?
  if is_secure
    secure(Project) { Project.find(self[:project_id]) }
  else
    secure(Node, :secure => false) { Project.find(self[:project_id]) }
  end
end

#real_section(is_secure = true) ⇒ Object

Find real section



1150
1151
1152
1153
1154
1155
1156
1157
1158
# File 'app/models/node.rb', line 1150

def real_section(is_secure = true)
  return self if self[:parent_id].nil? # root
  # we cannot use Section to find because the root node behaves like a Section but is a Project.
  if is_secure
    secure(Node) { Node.find(self[:section_id]) }
  else
    secure(Node, :secure => false) { Node.find(self[:section_id]) }
  end
end

#reloadObject

Remove loaded version and properties on reload.



1018
1019
1020
1021
1022
# File 'app/models/node.rb', line 1018

def reload
  @version    = nil
  @properties = nil
  super
end

#replace_attributes_in_values(hash) ⇒ Object

Replace [id], [title], etc in attributes values



1076
1077
1078
1079
1080
# File 'app/models/node.rb', line 1076

def replace_attributes_in_values(hash)
  hash.each do |k,v|
    hash[k] = safe_eval_string(v)
  end
end

#safe_method_type(signature, receiver = nil) ⇒ Object

We use the virtual_class as proxy for method type resolution. def safe_eval(code)

eval RubyLess.translate(schema, code)

end



144
145
146
# File 'app/models/node.rb', line 144

def safe_method_type(signature, receiver = nil)
  schema.safe_method_type(signature, receiver)
end

#safe_send(method) ⇒ Object

Safe dynamic method dispatching when the method is not known during compile time. Currently this only works for methods without arguments.



1471
1472
1473
1474
1475
# File 'app/models/node.rb', line 1471

def safe_send(method)
  return nil unless type = virtual_class.safe_method_type([method])
  res = eval(type[:method])
  res ? res.to_s : nil
end

#sectionObject

Return self if the current node is a section else find section.



1145
1146
1147
# File 'app/models/node.rb', line 1145

def section
  self.kind_of?(Section) ? self : real_section
end

#section_zipObject

Id to zip mapping for section_id. Used by zafu and forms.



1271
1272
1273
# File 'app/models/node.rb', line 1271

def section_zip
  section[:zip]
end

#skinObject



1176
1177
1178
# File 'app/models/node.rb', line 1176

def skin
  @skin ||= secure(Skin) { o_skin }
end

#skin_zipObject



1266
1267
1268
# File 'app/models/node.rb', line 1266

def skin_zip
  @skin_zip || skin.try(:zip)
end

#skin_zip=(zip) ⇒ Object

When setting skin trough controllers, we receive skin_zip=.



1262
1263
1264
# File 'app/models/node.rb', line 1262

def skin_zip=(zip)
  @skin_zip = zip.to_i
end

#sweep_cache(filter = nil) ⇒ Object



1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
# File 'app/models/node.rb', line 1353

def sweep_cache(filter = nil)
  return true if current_site.being_created?

  # Clear element cache
  # Partial cache not used
  # Cache.sweep(:visitor_id=>self[:user_id], :visitor_groups=>[rgroup_id, wgroup_id, dgroup_id], :kpath=>self.vclass.kpath)

  # Clear full result cache

  # we want to be sure to find the project and parent, even if the visitor does not have an
  # access to these elements.
  # FIXME: use self + modified relations instead of parent/project
  [self, self.real_project(false), self.real_section(false), self.parent(false)].compact.uniq.each do |obj|
    # destroy all pages in project, parent and section !
    CachedPage.expire_with(obj, filter)
  end

  # clear assets
  FileUtils::rmtree(asset_path(''))
  true
end

#to_yamlObject

export node as a hash



1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
# File 'app/models/node.rb', line 1435

def to_yaml
  hash = {}
  export_keys[:zazen].each do |k, v|
    hash[k] = unparse_assets(v, self, k)
  end

  export_keys[:dates].each do |k, v|
    hash[k] = visitor.tz.utc_to_local(v).strftime("%Y-%m-%d %H:%M:%S")
  end
  
  hash.merge!('class' => self.klass, 'position' => self.position)
  hash.to_yaml
end

#unparse_assets(text, helper, key) ⇒ Object

Parse text and replace ids ‘!30!’ by their pseudo path ‘!(img/bird)!’ key is used in TextDocument overloaded method.



1091
1092
1093
# File 'app/models/node.rb', line 1091

def unparse_assets(text, helper, key)
  ZazenParser.new(text,:helper=>helper).render(:translate_ids => :relative_path, :node=>self)
end

#update_attributes_with_transformation(new_attributes, change_timezone = true) ⇒ Object

Update a node’s attributes, transforming the attributes first from the visitor’s context to Node context.



1071
1072
1073
# File 'app/models/node.rb', line 1071

def update_attributes_with_transformation(new_attributes, change_timezone = true)
  update_attributes(secure(Node) {Node.transform_attributes(new_attributes, self, change_timezone)})
end

#userObject

TODO: why do we need secure here ?



1206
1207
1208
# File 'app/models/node.rb', line 1206

def user
  secure!(User) { o_user }
end

#user_zipObject

Id to zip mapping for user_id. Used by zafu and forms.



1281
# File 'app/models/node.rb', line 1281

def user_zip; self[:user_id]; end

#v_numberObject



295
296
297
# File 'app/models/node.rb', line 295

def v_number
  version.number
end

#vclassObject

virtual class FIXME: alias vclass to virtual_class alias vclass virtual_class



1046
1047
1048
# File 'app/models/node.rb', line 1046

def vclass
  virtual_class || self.class
end

#versions_with_secure(*args) ⇒ Object

TODO: remove when :inverse_of works.



1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
# File 'app/models/node.rb', line 1025

def versions_with_secure(*args)
  proxy = versions_without_secure(*args)
  if frozen?
    proxy = []
  elsif proxy.loaded?
    proxy.each do |v|
      v.node = self
    end
  end
  proxy
end

#virtual_classObject Also known as: schema



108
109
110
111
112
113
114
# File 'app/models/node.rb', line 108

def virtual_class
  @virtual_class ||= if self[:vclass_id]
    VirtualClass.find_by_id(self[:vclass_id])
  else
    VirtualClass.find_by_name(self.class.name)
  end
end

#virtual_class=(vclass) ⇒ Object



120
121
122
123
124
# File 'app/models/node.rb', line 120

def virtual_class=(vclass)
  @virtual_class = vclass
  self[:vclass_id] = vclass.id
  self[:kpath] = vclass.kpath
end

#vkind_of?(klass) ⇒ Boolean

include virtual classes to check inheritance chain

Returns:

  • (Boolean)


1064
1065
1066
1067
1068
# File 'app/models/node.rb', line 1064

def vkind_of?(klass)
  if virt = VirtualClass[klass.to_s]
    kpath_match?(virt.kpath)
  end
end

#zafu_eval(str, opts = {}) ⇒ Object

Enable dynamic property evaluation



1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
# File 'app/models/node.rb', line 1002

def zafu_eval(str, opts = {})
  value = safe_eval(str)
  if value.kind_of?(String)
    value
  elsif value.kind_of?(Time)
    format_date(value, opts)
  elsif value.kind_of?(Array)
    value.join(',')
  else
    value.to_s
  end
rescue RubyLess::Error
  nil
end

#zafu_versionsObject



997
998
999
# File 'app/models/node.rb', line 997

def zafu_versions
  versions.all(:order => 'number desc')
end