comfortable_mexican_sofa 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/Gemfile +1 -1
  2. data/Gemfile.lock +1 -1
  3. data/README.md +83 -1
  4. data/VERSION +1 -1
  5. data/app/controllers/cms_admin/base_controller.rb +15 -0
  6. data/app/controllers/cms_admin/layouts_controller.rb +3 -3
  7. data/app/controllers/cms_admin/pages_controller.rb +8 -8
  8. data/app/controllers/cms_admin/sites_controller.rb +58 -0
  9. data/app/controllers/cms_admin/snippets_controller.rb +18 -14
  10. data/app/controllers/cms_admin/uploads_controller.rb +7 -5
  11. data/app/controllers/cms_content_controller.rb +17 -5
  12. data/app/models/cms_layout.rb +7 -7
  13. data/app/models/cms_page.rb +18 -4
  14. data/app/models/cms_site.rb +23 -0
  15. data/app/models/cms_snippet.rb +17 -12
  16. data/app/models/cms_upload.rb +4 -7
  17. data/app/views/cms_admin/layouts/_form.html.erb +1 -1
  18. data/app/views/cms_admin/layouts/index.html.erb +1 -1
  19. data/app/views/cms_admin/pages/_form.html.erb +3 -2
  20. data/app/views/cms_admin/pages/_form_blocks.html.erb +1 -1
  21. data/app/views/cms_admin/pages/_index_branch.html.erb +1 -1
  22. data/app/views/cms_admin/pages/index.html.erb +1 -1
  23. data/app/views/cms_admin/sites/_form.html.erb +2 -0
  24. data/app/views/cms_admin/sites/edit.html.erb +6 -0
  25. data/app/views/cms_admin/sites/index.html.erb +22 -0
  26. data/app/views/cms_admin/sites/new.html.erb +6 -0
  27. data/app/views/cms_admin/snippets/_form.html.erb +2 -1
  28. data/app/views/cms_admin/snippets/index.html.erb +20 -1
  29. data/app/views/layouts/cms_admin.html.erb +2 -1
  30. data/comfortable_mexican_sofa.gemspec +31 -9
  31. data/config/initializers/comfortable_mexican_sofa.rb +10 -0
  32. data/config/routes.rb +3 -1
  33. data/db/migrate/01_create_cms.rb +18 -4
  34. data/lib/comfortable_mexican_sofa.rb +28 -22
  35. data/lib/comfortable_mexican_sofa/acts_as_tree.rb +97 -0
  36. data/lib/comfortable_mexican_sofa/cms_tag/snippet.rb +4 -0
  37. data/lib/comfortable_mexican_sofa/configuration.rb +19 -0
  38. data/lib/comfortable_mexican_sofa/controller_methods.rb +41 -0
  39. data/lib/comfortable_mexican_sofa/{cms_engine.rb → engine.rb} +1 -1
  40. data/lib/comfortable_mexican_sofa/{cms_form_builder.rb → form_builder.rb} +1 -1
  41. data/lib/comfortable_mexican_sofa/http_auth.rb +17 -0
  42. data/lib/comfortable_mexican_sofa/rails_extensions.rb +11 -0
  43. data/lib/comfortable_mexican_sofa/view_methods.rb +33 -0
  44. data/lib/generators/cms_generator.rb +4 -0
  45. data/public/stylesheets/comfortable_mexican_sofa/structure.css +10 -12
  46. data/test/fixtures/cms_layouts.yml +3 -1
  47. data/test/fixtures/cms_pages.yml +6 -2
  48. data/test/fixtures/cms_sites.yml +3 -0
  49. data/test/fixtures/cms_snippets.yml +3 -1
  50. data/test/fixtures/cms_uploads.yml +1 -0
  51. data/test/functional/cms_admin/layouts_controller_test.rb +3 -1
  52. data/test/functional/cms_admin/pages_controller_test.rb +4 -2
  53. data/test/functional/cms_admin/sites_controller_test.rb +92 -0
  54. data/test/functional/cms_admin/snippets_controller_test.rb +62 -37
  55. data/test/functional/cms_content_controller_test.rb +32 -5
  56. data/test/integration/authentication_test.rb +27 -0
  57. data/test/integration/render_cms_test.rb +57 -0
  58. data/test/integration/sites_test.rb +30 -0
  59. data/test/test_helper.rb +46 -3
  60. data/test/unit/cms_block_test.rb +1 -0
  61. data/test/unit/cms_configuration_test.rb +16 -0
  62. data/test/unit/cms_layout_test.rb +3 -3
  63. data/test/unit/cms_page_test.rb +23 -13
  64. data/test/unit/cms_site_test.rb +41 -0
  65. data/test/unit/cms_snippet_test.rb +1 -1
  66. data/test/unit/cms_tags/snippet_test.rb +1 -1
  67. data/test/unit/cms_upload_test.rb +7 -6
  68. metadata +32 -10
  69. data/lib/comfortable_mexican_sofa/cms_acts_as_tree.rb +0 -101
  70. data/lib/comfortable_mexican_sofa/cms_rails_extensions.rb +0 -32
  71. data/test/functional/cms_admin/base_controller_test.rb +0 -9
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'rails', '3.0.0'
3
+ gem 'rails', '>=3.0.0'
4
4
  gem 'sqlite3-ruby', :require => 'sqlite3'
5
5
  gem 'active_link_to', '>=0.0.6'
6
6
  gem 'paperclip', '>=2.3.3'
data/Gemfile.lock CHANGED
@@ -87,5 +87,5 @@ DEPENDENCIES
87
87
  jeweler (>= 1.4.0)
88
88
  mime-types
89
89
  paperclip (>= 2.3.3)
90
- rails (= 3.0.0)
90
+ rails (>= 3.0.0)
91
91
  sqlite3-ruby
data/README.md CHANGED
@@ -1,4 +1,86 @@
1
1
  Comfortable Mexican Sofa (CMS)
2
2
  ==============================
3
3
 
4
- TODO
4
+ What is this?
5
+ -------------
6
+ Comfortable Mexican Sofa is a Content Management System with an obnoxious name. Also it's a Rails 3 Engine. This means that you can use it as a stand-alone application and also as an Engine for your existing application.
7
+
8
+ Installation
9
+ ------------
10
+
11
+ ### Stand-alone
12
+ TODO: Need to create some sort of setup, so you can simply run:
13
+
14
+ $ comfortable_mexican_sofa my_new_app
15
+
16
+ ### As a Rails Engine
17
+ Add gem definition to your Gemfile:
18
+
19
+ config.gem 'comfortable_mexican_sofa'
20
+
21
+ Then from the Rails project's root run:
22
+
23
+ bundle install
24
+ rails g cms
25
+ rake db:migrate
26
+
27
+ At this point you should have database structure created, some assets copied to /public directory and initializer set up:
28
+
29
+ ComfortableMexicanSofa.configure do |config|
30
+ config.cms_title = 'ComfortableMexicanSofa'
31
+ config.authentication = 'ComfortableMexicanSofa::HttpAuth'
32
+ end
33
+
34
+ # Credentials for CmsHttpAuthentication
35
+ ComfortableMexicanSofa::HttpAuth.username = 'username'
36
+ ComfortableMexicanSofa::HttpAuth.password = 'password'
37
+
38
+ Usage
39
+ -----
40
+
41
+ Now you should be able to navigate to http://yoursite/cms-admin
42
+
43
+ ### Step 1: Create Site
44
+ CMS allows you to run multiple sites from a single installation. Each site is attached to a hostname. For the first time you'll be prompted to set up the initial site. Hostname will be pre-populated so just choose a label.
45
+
46
+ ### Step 2: Create Layout
47
+ Before creating pages and populating them with content we need to create a layout. Layout is the template of your pages. It defines some reusable content (like header and footer, for example) and places where the content goes. A very simple layout can look like this:
48
+
49
+ <html>
50
+ <body>
51
+ <h1>My Awesome Site</h1>
52
+ <cms:page:content>
53
+ </body>
54
+ </html>
55
+
56
+ So there's your layout and the `<cms:page:content>` defines a place where renderable `content` will go. There's just a handful of tags that you can use.
57
+
58
+ *Page Blocks* are pieces of content that will be output on the page:
59
+
60
+ <cms:page:some_label:text> # same as <cms:page:some_label>, will render a text area during page creation
61
+ <cms:page:some_label:string> # will render a text field during page creation
62
+ <cms:page:some_label:datetime> # datetime select widget
63
+ <cms:page:some_label:integer> # a number field
64
+
65
+ *Page Fields* are pieces of content that are not rendered. They are useful for hidden values you want to use inside your app.
66
+
67
+ <cms:field:some_label:text> # text area for the page creation form
68
+ <cms:field:some_label:string> # same as <cms:field:some_label>, this is a text field
69
+ <cms:field:some_label:datetime> # datetime
70
+ <cms:field:some_label:integer> # a number field
71
+
72
+ *Snippets* bits of reusable content that can be used in pages and layouts
73
+
74
+ <cms:snippet:snippet_slug>
75
+
76
+ *Partials* are exactly that. You don't want to do IRB inside CMS so there's a handy tag:
77
+
78
+ <cms:partial:path/to/partial>
79
+
80
+ You don't have to define entire html layout, however. You can simply re-use your application one. Page content will be yielded into it like any normal view.
81
+
82
+ TODO: more stuff
83
+
84
+ ### Step 3: Create Page
85
+
86
+ TODO: You pres butan page is created. Yay!
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.0.2
@@ -1,5 +1,20 @@
1
+ # Authentication module must have #authenticate method
2
+ include ComfortableMexicanSofa.config.authentication.to_s.constantize
3
+
1
4
  class CmsAdmin::BaseController < ApplicationController
2
5
 
6
+ before_filter :authenticate,
7
+ :load_admin_cms_site
8
+
3
9
  layout 'cms_admin'
10
+
11
+ protected
4
12
 
13
+ def load_admin_cms_site
14
+ @cms_site = CmsSite.find_by_hostname!(request.host.downcase)
15
+ rescue ActiveRecord::RecordNotFound
16
+ flash[:error] = 'No Site defined for this hostname. Create it now.'
17
+ redirect_to new_cms_admin_site_path
18
+ end
19
+
5
20
  end
@@ -4,7 +4,7 @@ class CmsAdmin::LayoutsController < CmsAdmin::BaseController
4
4
  before_filter :load_cms_layout, :only => [:edit, :update, :destroy]
5
5
 
6
6
  def index
7
- @cms_layouts = CmsLayout.roots
7
+ @cms_layouts = @cms_site.cms_layouts.roots
8
8
  end
9
9
 
10
10
  def new
@@ -42,12 +42,12 @@ class CmsAdmin::LayoutsController < CmsAdmin::BaseController
42
42
  protected
43
43
 
44
44
  def build_cms_layout
45
- @cms_layout = CmsLayout.new(params[:cms_layout])
45
+ @cms_layout = @cms_site.cms_layouts.new(params[:cms_layout])
46
46
  @cms_layout.parent ||= CmsLayout.find_by_id(params[:parent_id])
47
47
  end
48
48
 
49
49
  def load_cms_layout
50
- @cms_layout = CmsLayout.find(params[:id])
50
+ @cms_layout = @cms_site.cms_layouts.find(params[:id])
51
51
  rescue ActiveRecord::RecordNotFound
52
52
  flash[:error] = 'Layout not found'
53
53
  redirect_to :action => :index
@@ -4,7 +4,7 @@ class CmsAdmin::PagesController < CmsAdmin::BaseController
4
4
  before_filter :load_cms_page, :only => [:edit, :update, :destroy]
5
5
 
6
6
  def index
7
- @cms_pages = [CmsPage.root].compact
7
+ @cms_pages = [@cms_site.cms_pages.root].compact
8
8
  end
9
9
 
10
10
  def new
@@ -40,21 +40,21 @@ class CmsAdmin::PagesController < CmsAdmin::BaseController
40
40
  end
41
41
 
42
42
  def form_blocks
43
- @cms_page = CmsPage.find_by_id(params[:id]) || CmsPage.new
44
- @cms_page.cms_layout = CmsLayout.find_by_id(params[:layout_id])
43
+ @cms_page = @cms_site.cms_pages.find_by_id(params[:id]) || CmsPage.new
44
+ @cms_page.cms_layout = @cms_site.cms_layouts.find_by_id(params[:layout_id])
45
45
  end
46
46
 
47
47
  protected
48
48
 
49
49
  def build_cms_page
50
- @cms_page = CmsPage.new(params[:cms_page])
51
- @cms_page.parent ||= (CmsPage.find_by_id(params[:parent_id]) || CmsPage.root)
52
- @cms_page.cms_layout ||= (@cms_page.parent && @cms_page.parent.cms_layout || CmsLayout.first)
50
+ @cms_page = @cms_site.cms_pages.new(params[:cms_page])
51
+ @cms_page.parent ||= (CmsPage.find_by_id(params[:parent_id]) || @cms_site.cms_pages.root)
52
+ @cms_page.cms_layout ||= (@cms_page.parent && @cms_page.parent.cms_layout || @cms_site.cms_layouts.first)
53
53
  end
54
54
 
55
55
  def load_cms_page
56
- @cms_page = CmsPage.find(params[:id])
57
- @cms_page.cms_layout ||= (@cms_page.parent && @cms_page.parent.cms_layout || CmsLayout.first)
56
+ @cms_page = @cms_site.cms_pages.find(params[:id])
57
+ @cms_page.cms_layout ||= (@cms_page.parent && @cms_page.parent.cms_layout || @cms_site.cms_layouts.first)
58
58
  rescue ActiveRecord::RecordNotFound
59
59
  flash[:error] = 'Page not found'
60
60
  redirect_to :action => :index
@@ -0,0 +1,58 @@
1
+ class CmsAdmin::SitesController < CmsAdmin::BaseController
2
+
3
+ skip_before_filter :load_admin_cms_site
4
+
5
+ before_filter :build_cms_site, :only => [:new, :create]
6
+ before_filter :load_cms_site, :only => [:edit, :update, :destroy]
7
+
8
+ def index
9
+ @cms_sites = CmsSite.all
10
+ end
11
+
12
+ def new
13
+ render
14
+ end
15
+
16
+ def edit
17
+ render
18
+ end
19
+
20
+ def create
21
+ @cms_site.save!
22
+ flash[:notice] = 'Site created'
23
+ redirect_to :action => :edit, :id => @cms_site
24
+ rescue ActiveRecord::RecordInvalid
25
+ flash.now[:error] = 'Failed to create site'
26
+ render :action => :new
27
+ end
28
+
29
+ def update
30
+ @cms_site.update_attributes!(params[:cms_site])
31
+ flash[:notice] = 'Site updated'
32
+ redirect_to :action => :edit, :id => @cms_site
33
+ rescue ActiveRecord::RecordInvalid
34
+ flash.now[:error] = 'Failed to update site'
35
+ render :action => :edit
36
+ end
37
+
38
+ def destroy
39
+ @cms_site.destroy
40
+ flash[:notice] = 'Site deleted'
41
+ redirect_to :action => :index
42
+ end
43
+
44
+ protected
45
+
46
+ def build_cms_site
47
+ @cms_site = CmsSite.new(params[:cms_site])
48
+ @cms_site.hostname ||= request.host.downcase
49
+ end
50
+
51
+ def load_cms_site
52
+ @cms_site = CmsSite.find(params[:id])
53
+ rescue ActiveRecord::RecordNotFound
54
+ flash[:error] = 'Site not found'
55
+ redirect_to :action => :index
56
+ end
57
+
58
+ end
@@ -1,32 +1,35 @@
1
1
  class CmsAdmin::SnippetsController < CmsAdmin::BaseController
2
- before_filter :build_cms_snippet,
3
- :only => [:new, :create]
4
- before_filter :load_cms_snippet,
5
- :only => [:edit, :update, :destroy]
6
-
2
+
3
+ before_filter :build_cms_snippet, :only => [:new, :create]
4
+ before_filter :load_cms_snippet, :only => [:edit, :update, :destroy]
5
+
7
6
  def index
8
- @cms_snippets = CmsSnippet.all(:order => 'label')
7
+ @cms_snippets = @cms_site.cms_snippets.all(:order => 'label')
9
8
  end
10
9
 
11
10
  def new
11
+ render
12
+ end
13
+
14
+ def edit
15
+ render
12
16
  end
13
17
 
14
18
  def create
15
19
  @cms_snippet.save!
16
- flash[:notice] = 'Snippet saved'
20
+ flash[:notice] = 'Snippet created'
17
21
  redirect_to :action => :edit, :id => @cms_snippet
18
22
  rescue ActiveRecord::RecordInvalid
23
+ flash.now[:error] = 'Failed to create snippet'
19
24
  render :action => :new
20
25
  end
21
26
 
22
- def edit
23
- end
24
-
25
27
  def update
26
28
  @cms_snippet.update_attributes!(params[:cms_snippet])
27
- flash[:notice] = 'Snippet saved'
29
+ flash[:notice] = 'Snippet updated'
28
30
  redirect_to :action => :edit, :id => @cms_snippet
29
31
  rescue ActiveRecord::RecordInvalid
32
+ flash.now[:error] = 'Failed to update snippet'
30
33
  render :action => :edit
31
34
  end
32
35
 
@@ -35,14 +38,15 @@ class CmsAdmin::SnippetsController < CmsAdmin::BaseController
35
38
  flash[:notice] = 'Snippet deleted'
36
39
  redirect_to :action => :index
37
40
  end
38
-
41
+
39
42
  protected
43
+
40
44
  def build_cms_snippet
41
- @cms_snippet = CmsSnippet.new(params[:cms_snippet])
45
+ @cms_snippet = @cms_site.cms_snippets.new(params[:cms_snippet])
42
46
  end
43
47
 
44
48
  def load_cms_snippet
45
- @cms_snippet = CmsSnippet.find(params[:id])
49
+ @cms_snippet = @cms_site.cms_snippets.find(params[:id])
46
50
  rescue ActiveRecord::RecordNotFound
47
51
  flash[:error] = 'Snippet not found'
48
52
  redirect_to :action => :index
@@ -1,12 +1,13 @@
1
1
  class CmsAdmin::UploadsController < CmsAdmin::BaseController
2
- before_filter :load_cms_upload,
3
- :only => :destroy
2
+
3
+ before_filter :load_cms_upload, :only => :destroy
4
4
 
5
5
  def index
6
+ render
6
7
  end
7
8
 
8
9
  def create
9
- @cms_upload = CmsUpload.new(:uploaded_file => params[:file])
10
+ @cms_upload = @cms_site.cms_uploads.new(:uploaded_file => params[:file])
10
11
  if @cms_upload.save
11
12
  render(:partial => 'cms_admin/uploads/upload', :object => @cms_upload)
12
13
  else
@@ -17,10 +18,11 @@ class CmsAdmin::UploadsController < CmsAdmin::BaseController
17
18
  def destroy
18
19
  @cms_upload.destroy
19
20
  end
20
-
21
+
21
22
  protected
23
+
22
24
  def load_cms_upload
23
- @cms_upload = CmsUpload.find(params[:id])
25
+ @cms_upload = @cms_site.cms_uploads.find(params[:id])
24
26
  rescue ActiveRecord::RecordNotFound
25
27
  render :nothing => true
26
28
  end
@@ -1,11 +1,12 @@
1
1
  class CmsContentController < ApplicationController
2
2
 
3
+ before_filter :load_cms_site
3
4
  before_filter :load_cms_page, :only => :render_html
4
5
  before_filter :load_cms_layout, :only => [:render_css, :render_js]
5
6
 
6
- def render_html
7
+ def render_html(status = 200)
7
8
  layout = @cms_page.cms_layout.app_layout.blank?? false : @cms_page.cms_layout.app_layout
8
- render :inline => @cms_page.content, :layout => layout
9
+ render :inline => @cms_page.content, :layout => layout, :status => status
9
10
  end
10
11
 
11
12
  def render_css
@@ -18,14 +19,25 @@ class CmsContentController < ApplicationController
18
19
 
19
20
  protected
20
21
 
22
+ def load_cms_site
23
+ @cms_site = CmsSite.find_by_hostname!(request.host.downcase)
24
+ rescue ActiveRecord::RecordNotFound
25
+ render :text => 'Site Not Found', :status => 404
26
+ end
27
+
21
28
  def load_cms_page
22
- @cms_page = CmsPage.find_by_full_path!("/#{params[:cms_path]}")
29
+ @cms_page = @cms_site.cms_pages.find_by_full_path!("/#{params[:cms_path]}")
30
+ return redirect_to(@cms_page.target_page.full_path) if @cms_page.target_page
23
31
  rescue ActiveRecord::RecordNotFound
24
- render :text => 'Page not found', :status => 404
32
+ if @cms_page = @cms_site.cms_pages.find_by_full_path('/404')
33
+ render_html(404)
34
+ else
35
+ render :text => 'Page Not Found', :status => 404
36
+ end
25
37
  end
26
38
 
27
39
  def load_cms_layout
28
- @cms_layout = CmsLayout.find(params[:id])
40
+ @cms_layout = @cms_site.cms_layouts.find(params[:id])
29
41
  rescue ActiveRecord::RecordNotFound
30
42
  render :nothing => true, :status => 404
31
43
  end
@@ -3,23 +3,23 @@ class CmsLayout < ActiveRecord::Base
3
3
  acts_as_tree
4
4
 
5
5
  # -- Relationships --------------------------------------------------------
6
+ belongs_to :cms_site
6
7
  has_many :cms_pages, :dependent => :nullify
7
8
 
8
9
  # -- Validations ----------------------------------------------------------
9
- validates :label,
10
- :presence => true
11
- validates :content,
12
- :presence => true
10
+ validates :cms_site_id, :presence => true
11
+ validates :label, :presence => true
12
+ validates :content, :presence => true
13
13
 
14
14
  # -- Class Methods --------------------------------------------------------
15
15
  # Tree-like structure for layouts
16
- def self.options_for_select(cms_layout = nil, current_layout = nil, depth = 0, spacer = '. . ')
16
+ def self.options_for_select(cms_site, cms_layout = nil, current_layout = nil, depth = 0, spacer = '. . ')
17
17
  out = []
18
- [current_layout || CmsLayout.roots].flatten.each do |layout|
18
+ [current_layout || cms_site.cms_layouts.roots].flatten.each do |layout|
19
19
  next if cms_layout == layout
20
20
  out << [ "#{spacer*depth}#{layout.label}", layout.id ]
21
21
  layout.children.each do |child|
22
- out += options_for_select(cms_layout, child, depth + 1, spacer)
22
+ out += options_for_select(cms_site, cms_layout, child, depth + 1, spacer)
23
23
  end
24
24
  end
25
25
  return out.compact
@@ -6,7 +6,10 @@ class CmsPage < ActiveRecord::Base
6
6
  attr_accessor :cms_tags
7
7
 
8
8
  # -- Relationships --------------------------------------------------------
9
+ belongs_to :cms_site
9
10
  belongs_to :cms_layout
11
+ belongs_to :target_page,
12
+ :class_name => 'CmsPage'
10
13
  has_many :cms_blocks,
11
14
  :dependent => :destroy
12
15
  accepts_nested_attributes_for :cms_blocks
@@ -16,6 +19,8 @@ class CmsPage < ActiveRecord::Base
16
19
  after_save :sync_child_pages
17
20
 
18
21
  # -- Validations ----------------------------------------------------------
22
+ validates :cms_site_id,
23
+ :presence => true
19
24
  validates :label,
20
25
  :presence => true
21
26
  validates :slug,
@@ -26,15 +31,16 @@ class CmsPage < ActiveRecord::Base
26
31
  :presence => true
27
32
  validates :full_path,
28
33
  :presence => true,
29
- :uniqueness => true
34
+ :uniqueness => { :scope => :cms_site_id }
35
+ validate :validate_target_page
30
36
 
31
37
  # -- Class Methods --------------------------------------------------------
32
38
  # Tree-like structure for pages
33
- def self.options_for_select(cms_page = nil, current_page = nil, depth = 0, spacer = '. . ')
34
- return [] if (current_page ||= CmsPage.root) == cms_page || !current_page
39
+ def self.options_for_select(cms_site, cms_page = nil, current_page = nil, depth = 0, exclude_self = true, spacer = '. . ')
40
+ return [] if (current_page ||= cms_site.cms_pages.root) == cms_page && exclude_self || !current_page
35
41
  out = [[ "#{spacer*depth}#{current_page.label}", current_page.id ]]
36
42
  current_page.children.each do |child|
37
- out += options_for_select(cms_page, child, depth + 1, spacer)
43
+ out += options_for_select(cms_site, cms_page, child, depth + 1, exclude_self, spacer)
38
44
  end
39
45
  return out.compact
40
46
  end
@@ -59,6 +65,14 @@ protected
59
65
  self.full_path = self.parent ? "#{self.parent.full_path}/#{self.slug}".squeeze('/') : '/'
60
66
  end
61
67
 
68
+ def validate_target_page
69
+ return unless self.target_page
70
+ p = self
71
+ while p.target_page do
72
+ return self.errors.add(:target_page_id, 'Invalid Redirect') if (p = p.target_page) == self
73
+ end
74
+ end
75
+
62
76
  # Forcing re-saves for child pages so they can update full_paths
63
77
  def sync_child_pages
64
78
  children.each{ |p| p.save! } if full_path_changed?