Roy Wright <roywrig...@gmail.com> writes:

> On Sep 13, 2009, at 4:37 AM, Chris Hoeppner wrote:
>
>>
>> Hey,
>>
>> I'm building a small wiki "engine" using DataMapper to handle the
>> persistence. Let me explain my layout, and the riddle I encountered:
>>
>> * Everything is a Page.
>> * Pages can have many childs, but only one parent.
>> * Pages with the same parent need to have distinct labels (titles,
>>  names...)
>>
>> My first thought was adding :key => true to parent_id and label, but  
>> the
>> next second I thought that why I'd have all this as PK. Also, it would
>> force me to have *every* page parented somewhere. I could of course  
>> add
>> a Page called Wiki as root, or have root Pages come with a parent_id  
>> of
>> zero...
>>
>> This is where I thought I'd ask a bit around. I'm sure you DM wizards
>> will have a magic answer, or at least a hint in the right direction.
>>


> Maybe change the third requirement to be:
>
> * Pages must have distinct labels (titles, names...)
>
> then make the labels unique.

The ideal situation is that page names can be duplicate as long as they
are not siblings, so you can have a Foo page child of Bar, and another
Foo page, child of Baz. After all +/wiki/bar/foo+ is distinct to
+/wiki/baz/foo+, right? I'm nobody to tell my users where and how to
name their pages. Things should just work and not fail in cryptic
ways. A user does not need to know how I manage my IA.

I have made up a fairly hacky way of doing this. Here goes:

  class Page
    include DataMapper::Resource
  
    property :id, Serial
    property :parent_id, Integer, :unique_index => :path, :default => 0
    property :label, String, :unique_index => :path

    # Revisions handle the Page's content.
    # To access the latest thing:
    #     @page.revisions.last
    # or use the #body helper:
    #     @page.body
    # which yields the latest revision's content.
    has n, :revisions,
           :class_name => 'Wiki::Revision'

    # Can have one parent, and many child Pages.
    belongs_to :parent, :class_name => 'Wiki::Page', :child_key => [:parent_id]
    has n, :children, :class_name => 'Wiki::Page', :child_key => [:parent_id]
  end

I have left out all methods, for simplicity. I basically have methods
for walking the hierarchy, some string transformations, etc, but that's
out of the scope now.

As you can see, I've defined a property, :parent_id, with the same name
as the :child_key used for the self-referential relation. I actually
wrote this jokingly, but in awe discovered it to work.

As this is now, it defines a compound unique index on the label and
parent.

Here is the method I use to retrieve pages based on their path:

    def self.for_path(path)
      # Special page RootIndex
      return first(:label => "RootIndex") if path == ""

      leaf = path.split("/").last

      # Select all pages that match +leaf+, and then check the +#path+
      # method to see if any matches the +path+ argument.
      pages = all(:label => leaf.camel_case)
      matches = pages.select { |p| p.path == path}

      # This returns the first array element. IF there are more, they
      # are ignored.
      matches.first || nil
    end

It first declares a special case for a "magic" root index page, and then
proceeds to work on the given path. It finds all pages who's :label
matches the end component of the path, and then checks the #path on each
page to see if it matches.

The path is built as follows:

    def path
      @path ||= parents.map {|p| p.label.snake_case }.join("/") / 
label.snake_case
    end

where #parents returns an array of ancestors, in the correct order (root
page first, and it's childs in descending order).

The whole endeavour is still missing some specs for certain cases, like
root pages (pages with no ancestors), but IMHO it's heading in the right
direction. You're welcome to prove me wrong, of course =)

> For a wiki, I'd also question your second requirement.  IMO, pages  
> should have n:n relationships.

Page relationships fill the role of categories. Everything being a page
greatly simplifies things. A page with no content renders a child
index. A page with content and childs will show it's childs at the
bottom, or in a sidebar, etc.

If you think about it, this is a pretty sane way of doing it. Or would
you rather have abstract categories and pages associated with them to
render stuff? Thought so.

-- 
Chris Hoeppner

El que nació para melón, nunca llegará a sandía. 

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"DataMapper" group.
To post to this group, send email to datamapper@googlegroups.com
To unsubscribe from this group, send email to 
datamapper+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/datamapper?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to