I want to create a CMS like site where the user starts off with a some generic pages, i.e.
- homepage
- about
- contact
- etc
and from there can add child pages dynamically, for example
- homepage
- articles
- article1
- something
- something-else
- something
- article2
- article1
- articles
- about
- contact
- etc
To achieve this I'm planning on using some kind of self-referential association like
class Page < ActiveRecord::Base
belongs_to :parent, :class_name => 'Page'
has_many :children, :class_name => 'Page'
end
The one thing I'm struggling with is the route generation. Because pages can be added on the fly I need to dynamically generate routes for these pages and there is no way of knowing how many levels deep a page may be nested
So if I start off with the homepage: /
and then start adding pages i.e.
/articles/article1/something/something-else/another-thing
How can something like that be achieved with the rails routing model?
-
You have to parse the route yourself
map.connect '*url', :controller => 'pages', :action => 'show'Now you should have a
params[:url]available in your action that is the request path as an array separated by the slashes. Once you have those strings its a simple matter to find the models you need from there.That was from memory, and it's been a long while. Hope it works for you.
-
One solution to this prob is to dynamically load routes from hooks on your models. From example, a snippet from the
Slugmodel on my site:class Slug < ActiveRecord::Base belongs_to :navigable validates_presence_of :name, :navigable_id validates_uniqueness_of :name after_save :update_route def add_route new_route = ActionController::Routing::Routes.builder.build(name, route_options) ActionController::Routing::Routes.routes.insert(0, new_route) end def remove_route ActionController::Routing::Routes.routes.reject! { |r| r.instance_variable_get(:@requirements)[:slug_id] == id } end def update_route remove_route add_route end def route_options @route_options ||= { :controller => navigable.controller, :action => navigable.action, :navigable_id => navigable_id, :slug_id => id } end endThis inserts the route at top priority (0 in the routing array in memory) after it has been saved.
Also, it sounds like you should be using a tree management plugin and like awesome nested set or better nested set to manage the tree for your site.
-
Look at RadiantCMS sources, they implement that functionality as far as i understand their self description.
-
Once you have some way to generate the URL string for your
Pagerecords (and I'll leave that part up to you), you can just map every page inconfig/routes.rb:Page.all.each do |page| map.connect page.url, :controller => 'pages', :action => 'show', :id => page endAnd have an observer hook the page model to reload routes when something changes:
class PageObserver < ActiveRecord::Observer def reload_routes(page) ActionController::Routing::Routes.reload! end alias_method :after_save, :reload_routes alias_method :after_destroy, :reload_routes endDon't forget to edit
config/environment.rbto load the observer:# Activate observers that should always be running config.active_record.observers = :page_observer -
Doesn't work in rails3. My guess is that rails3 parses the entire block for " get '/blah/foo' statements" without using "map." . I really hate that magic there. How did you solve the problem? Maybe we should open a bug.
best,
-dennis
-
Hi, I've implemented a similar functionality into a Rails gem, using self referential associations and a tree like js interface for reordering and nesting the "pages".
Templating language and authentication/authorization are left for the developer to implement. https://github.com/maca/tiny_cms
0 comments:
Post a Comment