Topic: Content controller

Hi guys,

I've been asked to create a fairly simple website for someone in which they'd be able to edit the content themselves (without any knowledge of HTML or coding in general.) The pages have to be hierarchical (one page belongs_to another) and the navigation menu has to be dynamic, adjusting itself to the pages the user creates.

In itself this is simple, I created a table called pages with columns id (int), content (text), parent (int). In the navigation menu I just read all the pages with parent == 0 first and work my way down recursively.

Now my question: my friend asked me to add another feature to the site which would require a completely seperate controller but he still wants to be able to add it to (and move it around) the navigation himself. Also he wants menu items to be able to link to external urls.

What I did was create a special case in the Pages model where, if the content column starts with a ! it takes the word following it to be the name of the controller to link to, otherwise it simply links to the PageController and displays the content. Similarly if the content starts with a # it's assumed to be a link to an external url.

Somehow this solution feels wrong, my gut feeling is I should be doing something with polymorphic associations or some such.. Any thoughts would be much appreciated smile

-- marsvin

Re: Content controller

marsvin wrote:

In itself this is simple, I created a table called pages with columns id (int), content (text), parent (int). In the navigation menu I just read all the pages with parent == 0 first and work my way down recursively.

I recommend renaming the "parent" column to "parent_id", you can then use acts_as_tree in the Page model. This adds cool methods such as the "children" method and "ancestors" method you can use to help make the menu and breadcrumbs respectively.

marsvin wrote:

What I did was create a special case in the Pages model where, if the content column starts with a ! it takes the word following it to be the name of the controller to link to, otherwise it simply links to the PageController and displays the content. Similarly if the content starts with a # it's assumed to be a link to an external url.

Hmm, you may want to look into Single Table Inheritence. This way a page can have a certain "type". It can be a "RedirectPage" or a "ContentPage", both of which behave differently.

Railscasts - Free Ruby on Rails Screencasts

Re: Content controller

ryanb wrote:

Hmm, you may want to look into Single Table Inheritence. This way a page can have a certain "type". It can be a "RedirectPage" or a "ContentPage", both of which behave differently.

Thanks for that answer Ryan. Associations still confuse me a bit though.. I looked at the ForumExample on the rails wiki and some of your other posts, here's what I came up with. Please feel free to correct me where I'm getting this horribly wrong wink

# The database migration
class CreatePages < ActiveRecord::Migration
  def self.up
    create_table :pages do |t|
      t.column :name, :string
      t.column :content, :string
      t.column :parent_id, :int
      t.column :position, :int # Added a "position" column to address sorting of the navi menu
    end
  end

  def self.down
    drop_table :pages
  end
end

# The models
class Page < ActiveRecord::Base
  acts_as_tree :order => 'position'
end

class ContentPage < Page
  # The url method would return the url to link to from the navigation menu
  def url
    "/page/read/#{id}"
  end
end

class RedirectPage < Page
  def url
    "#{content}"
  end
end


Now this work in the sense that I can do something like ContentPage.new or RedirectPage.new but when I query a list of all pages to display the menu I don't know how to distinguish between Content and RedirectPages from the view.

To test I created one ContentPage with content "This is a test" and one RedirectPage with content "http://www.google.com/".

# In the console

>> p = Page.find :all
# snipped results hash
>> p[0].url
NoMethodError: undefined method 'url' for #<Page:0x2b7e4fd02b88>
# snipped rest of error
>> p[1].url
NoMethodError: undefined method 'url' for #<Page:0x2b7e4fd02ac0>
# snipped rest of error

>> p = ContentPage.find :all
# snipped results hash
>> p[0].url
=> "/page/read/2"
>> p[1].url
=> "/page/read/1"

>> p = RedirectPage.find :all
# snipped results hash
>> p[0].url
=> "This is a test"
>> p[1].url
=> "http://www.google.com"

Last edited by marsvin (2007-02-10 15:08:20)

Re: Content controller

You need to add a "type" column of type string to the table:

    create_table :pages do |t|
      t.column :name, :string
      t.column :content, :string
      t.column :parent_id, :int
      t.column :position, :int # Added a "position" column to address sorting of the navi menu
      t.column :type, :string
    end

This is used by single table inheritence to determine what kind of page it is.

Railscasts - Free Ruby on Rails Screencasts

Re: Content controller

Ah that was simple enough ^_^ Thanks a bunch Ryan it works perfectly now.