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.



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

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



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

def auto_create_discussion
  false
end

.cast_to_class(type) ⇒ Object

Return class of cast value.



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

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



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

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

.class_for_relation(rel) ⇒ Object

FIXME: Where is this used ?



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

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



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

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.



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

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.



661
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
# File 'app/models/node.rb', line 661

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



567
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
# File 'app/models/node.rb', line 567

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



826
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
# File 'app/models/node.rb', line 826

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



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

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.



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

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)


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

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.



517
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
# File 'app/models/node.rb', line 517

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



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

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



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

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.



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

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



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

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.



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

def inspect
  to_s
end

.kpath_match?(kpath) ⇒ Boolean

check inheritance chain through kpath

Returns:

  • (Boolean)


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

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



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

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



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

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.



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

def native_classes
  load_unhandled_children
  @@native_node_classes
end

.native_classes_by_nameObject

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



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

def native_classes_by_name
  load_unhandled_children
  @@native_node_classes_by_name
end

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



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

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.



619
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
# File 'app/models/node.rb', line 619

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)


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

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.



901
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
# File 'app/models/node.rb', line 901

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.



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

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.



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

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



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

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



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

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)



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

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



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

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)


1354
1355
1356
1357
1358
1359
# File 'app/models/node.rb', line 1354

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)


1402
1403
1404
# File 'app/models/node.rb', line 1402

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

#commentsObject

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



1383
1384
1385
1386
1387
1388
1389
1390
# File 'app/models/node.rb', line 1383

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…



1393
1394
1395
1396
1397
1398
1399
# File 'app/models/node.rb', line 1393

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.



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

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



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

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



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

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



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

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)


1429
1430
1431
1432
# File 'app/models/node.rb', line 1429

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.



1503
1504
1505
1506
1507
1508
# File 'app/models/node.rb', line 1503

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



1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
# File 'app/models/node.rb', line 1455

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



1516
1517
1518
# File 'app/models/node.rb', line 1516

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)



1230
1231
1232
# File 'app/models/node.rb', line 1230

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.



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

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.



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

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

#klassObject



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

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

#klass=(str) ⇒ Object



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

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)


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

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

#m_authorObject



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

def m_author; ''; end

#m_author=(str) ⇒ Object



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

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=, …



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

def m_text; ''; end

#m_text=(str) ⇒ Object



1367
1368
1369
1370
# File 'app/models/node.rb', line 1367

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

#m_titleObject



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

def m_title; ''; end

#m_title=(str) ⇒ Object



1372
1373
1374
1375
# File 'app/models/node.rb', line 1372

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.



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

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



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

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



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

alias o_skin skin

#o_userObject



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

alias o_user user

#parent(is_secure = true) ⇒ Object

Find parent



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

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.



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

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

#parent_zip=(zip) ⇒ Object

When setting parent trough controllers, we receive parent_zip=.



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

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



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

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



1511
1512
1513
# File 'app/models/node.rb', line 1511

def parse_keys
  export_keys[:zazen].keys
end

#projectObject

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



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

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

#project_zipObject

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



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

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.



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

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)



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

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



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

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.



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

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

#replace_attributes_in_values(hash) ⇒ Object

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



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

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.



1524
1525
1526
1527
1528
# File 'app/models/node.rb', line 1524

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.



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

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

#section_zipObject

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



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

def section_zip
  section[:zip]
end

#skinObject



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

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

#skin_zipObject



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

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

#skin_zip=(zip) ⇒ Object

When setting skin trough controllers, we receive skin_zip=.



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

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

#sweep_cache(filter = nil) ⇒ Object



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

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



1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
# File 'app/models/node.rb', line 1488

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.



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

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.



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

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 ?



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

def user
  secure!(User) { o_user }
end

#user_zipObject

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



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

def user_zip; self[:user_id]; end

#v_numberObject



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

def v_number
  version.number
end

#vclassObject

virtual class FIXME: alias vclass to virtual_class alias vclass virtual_class



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

def vclass
  virtual_class || self.class
end

#versions_with_secure(*args) ⇒ Object

TODO: remove when :inverse_of works.



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

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)


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

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



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

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



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

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