slices 1.0.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +85 -17
  4. data/app/assets/images/slices/i18n.png +0 -0
  5. data/app/assets/images/slices/icon_collapse.png +0 -0
  6. data/app/assets/javascripts/slices/app/helpers/apply_defaults.js +9 -0
  7. data/app/assets/javascripts/slices/app/helpers/composer.js +2 -0
  8. data/app/assets/javascripts/slices/app/helpers/i18n.js +7 -0
  9. data/app/assets/javascripts/slices/app/helpers/markdown_cheat_sheet.js +50 -0
  10. data/app/assets/javascripts/slices/app/helpers/radio_field.js +21 -0
  11. data/app/assets/javascripts/slices/app/helpers/sitemap.js +82 -1
  12. data/app/assets/javascripts/slices/app/helpers/uploader.js +1 -1
  13. data/app/assets/javascripts/slices/app/helpers/url_field.js +17 -0
  14. data/app/assets/javascripts/slices/app/slices.js +14 -11
  15. data/app/assets/javascripts/slices/app/views/composer_view.js +16 -0
  16. data/app/assets/javascripts/slices/app/views/radio_field_view.js +67 -0
  17. data/app/assets/javascripts/slices/app/views/token_field_view.js +81 -24
  18. data/app/assets/javascripts/slices/app/views/url_field_view.js +44 -0
  19. data/app/assets/javascripts/slices/slices.js +1 -0
  20. data/app/assets/javascripts/slices/vendor/jquery.js +4 -2
  21. data/app/assets/stylesheets/slices/admin.css.erb +156 -31
  22. data/app/controllers/admin/admin_controller.rb +16 -1
  23. data/app/controllers/admin/assets_controller.rb +1 -1
  24. data/app/controllers/admin/entries_controller.rb +1 -1
  25. data/app/controllers/admin/pages_controller.rb +4 -13
  26. data/app/controllers/admin/site_maps_controller.rb +1 -2
  27. data/app/controllers/slices_controller.rb +12 -1
  28. data/app/helpers/admin/admin_helper.rb +8 -0
  29. data/app/helpers/admin/site_maps_helper.rb +41 -6
  30. data/app/helpers/navigation_helper.rb +8 -3
  31. data/app/helpers/pages_helper.rb +2 -2
  32. data/app/helpers/snippets_helper.rb +26 -0
  33. data/app/models/admin.rb +2 -3
  34. data/app/models/asset.rb +9 -20
  35. data/app/models/attachment.rb +10 -1
  36. data/app/models/page.rb +45 -34
  37. data/app/models/site_map.rb +1 -2
  38. data/app/models/slice.rb +46 -1
  39. data/app/models/snippet.rb +13 -4
  40. data/app/presenters/page_presenter.rb +8 -13
  41. data/app/views/admin/auth/sessions/_form.html.erb +3 -0
  42. data/app/views/admin/pages/_fields.html.erb +1 -0
  43. data/app/views/admin/pages/_slices.html.erb +8 -4
  44. data/app/views/admin/pages/new.html.erb +5 -1
  45. data/app/views/admin/pages/show.html.erb +30 -14
  46. data/app/views/admin/site_maps/_page_actions.html.erb +2 -0
  47. data/app/views/admin/site_maps/_page_li.html.erb +18 -7
  48. data/app/views/admin/site_maps/_set_page_li.html.erb +5 -5
  49. data/app/views/layouts/admin.html.erb +0 -17
  50. data/config/routes.rb +13 -16
  51. data/lib/generators/slice/templates/presenter.rb +11 -12
  52. data/lib/generators/slice/templates/set.html.erb +2 -2
  53. data/lib/generators/slices/install_generator.rb +13 -3
  54. data/lib/generators/slices/templates/slices.rb +3 -4
  55. data/lib/mongo_search.rb +1 -1
  56. data/lib/slices.rb +2 -9
  57. data/lib/slices/asset/maker.rb +2 -3
  58. data/lib/slices/available_slices.rb +8 -1
  59. data/lib/slices/config.rb +49 -8
  60. data/lib/slices/engine.rb +0 -4
  61. data/lib/slices/has_attachments.rb +25 -37
  62. data/lib/slices/has_slices.rb +15 -8
  63. data/lib/slices/localized_fields.rb +9 -0
  64. data/lib/slices/page_as_json.rb +7 -1
  65. data/lib/slices/renderer.rb +2 -2
  66. data/lib/slices/tree.rb +12 -3
  67. data/lib/slices/version.rb +1 -1
  68. data/lib/tasks/db.rake +6 -10
  69. data/lib/tasks/seeds.rake +1 -1
  70. data/public/slices/templates/page_main.hbs +1 -1
  71. data/public/slices/templates/page_meta.hbs +2 -2
  72. metadata +56 -84
  73. data/app/views/admin/shared/_custom_links.html.erb +0 -1
  74. data/app/views/admin/shared/_custom_navigation.html.erb +0 -1
  75. data/app/views/layouts/slices/application.html.erb +0 -14
  76. data/lib/ext/file_store_cache.rb +0 -18
  77. data/lib/generators/slices/templates/mongoid.yml +0 -12
  78. data/lib/generators/templates/slices.rb +0 -209
  79. data/lib/rack_utf8_fix.rb +0 -10
  80. data/lib/set_link_renderer.rb +0 -31
  81. data/lib/slices/i18n.rb +0 -6
  82. data/lib/slices/i18n/backend.rb +0 -32
  83. data/lib/slices/will_paginate_mongoid.rb +0 -45
  84. data/lib/standard_tree.rb +0 -193
@@ -1,10 +1,25 @@
1
- class Admin::AdminController < SlicesController
1
+ class Admin::AdminController < ActionController::Base
2
2
  before_filter :authenticate_admin!
3
+ around_filter :set_locale
4
+
5
+ append_view_path(File.join(Rails.root, *%w[app slices]))
3
6
 
4
7
  protected
5
8
 
6
9
  def presenter_class(page_class)
7
10
  Object.const_get("#{page_class.name}Presenter")
8
11
  end
12
+
13
+ private
14
+
15
+ def request_locale
16
+ params[:locale]
17
+ end
18
+
19
+ def set_locale
20
+ I18n.with_locale(request_locale || I18n.default_locale) do
21
+ yield
22
+ end
23
+ end
9
24
  end
10
25
 
@@ -32,7 +32,7 @@ class Admin::AssetsController < Admin::AdminController
32
32
 
33
33
  def destroy
34
34
  asset = Asset.find(params[:id])
35
- asset.soft_destroy!
35
+ asset.destroy
36
36
  respond_to do |format|
37
37
  format.html { redirect_to admin_assets_path }
38
38
  format.json { render json: true }
@@ -4,7 +4,7 @@ class Admin::EntriesController < Admin::AdminController
4
4
  respond_to :json, :html
5
5
 
6
6
  def index
7
- @page = Page.find_by_id! params[:page_id]
7
+ @page = Page.find params[:page_id]
8
8
 
9
9
  respond_to do |format|
10
10
  format.html do
@@ -6,7 +6,6 @@ class Admin::PagesController < Admin::AdminController
6
6
  respond_to :text, only: [:show]
7
7
 
8
8
  before_filter :find_all_slices, only: :update
9
- after_filter :expire_fragments, only: [:update, :create, :destroy]
10
9
 
11
10
  def new
12
11
  @page = Page.new(parent_id: params[:parent_id]) # TODO: change parent_id to path
@@ -27,7 +26,7 @@ class Admin::PagesController < Admin::AdminController
27
26
  end
28
27
 
29
28
  def show
30
- page = Page.find_by_id!(params[:id])
29
+ page = Page.find(params[:id])
31
30
  @page = presenter_class(page.class).new(page)
32
31
  @layout = Layout.new(page.layout)
33
32
 
@@ -44,12 +43,12 @@ class Admin::PagesController < Admin::AdminController
44
43
  render hbs_path
45
44
  end
46
45
  end
47
- rescue Page::NotFound
46
+ rescue Mongoid::Errors::DocumentNotFound
48
47
  redirect_to admin_site_maps_path
49
48
  end
50
49
 
51
50
  def update
52
- @page = Page.find_by_id!(params[:id])
51
+ @page = Page.find(params[:id])
53
52
  if entry_page?
54
53
  params[:page][:set_slices] = params[:page].delete(:slices)
55
54
  end
@@ -64,7 +63,7 @@ class Admin::PagesController < Admin::AdminController
64
63
  end
65
64
 
66
65
  def destroy
67
- @page = Page.find_by_id!(params[:id])
66
+ @page = Page.find(params[:id])
68
67
  @page.destroy
69
68
  respond_to do |format|
70
69
  format.html { redirect_to admin_site_maps_path }
@@ -90,14 +89,6 @@ class Admin::PagesController < Admin::AdminController
90
89
  params.has_key?(:entries)
91
90
  end
92
91
 
93
- def expire_fragments
94
- record = @page
95
- type = record.class.to_s.underscore
96
- id = record.id.to_s
97
- expire_fragment(/.*#{type}.*/)
98
- expire_fragment(/.*#{id}.*/)
99
- end
100
-
101
92
  def find_all_slices
102
93
  Slices::AvailableSlices.all
103
94
  end
@@ -8,8 +8,7 @@ class Admin::SiteMapsController < Admin::AdminController
8
8
 
9
9
  def update
10
10
  SiteMap.rebuild(params[:sitemap])
11
- expire_fragment(/navigation/)
12
- head status: :ok
11
+ head :no_content
13
12
  end
14
13
  end
15
14
 
@@ -8,10 +8,11 @@ class SlicesController < ActionController::Base
8
8
 
9
9
  append_view_path(File.join(Rails.root, *%w[app slices]))
10
10
 
11
+ around_filter :set_locale
11
12
  define_callbacks :render_page, terminator: "response_body"
12
13
 
13
14
  def self.should_raise_exceptions?
14
- ! Rails.env.production?
15
+ Rails.application.config.consider_all_requests_local
15
16
  end
16
17
 
17
18
  protected
@@ -59,5 +60,15 @@ class SlicesController < ActionController::Base
59
60
  def page_layout(page)
60
61
  page.layout
61
62
  end
63
+
64
+ def request_locale
65
+ params[:locale]
66
+ end
67
+
68
+ def set_locale
69
+ I18n.with_locale(request_locale || I18n.default_locale) do
70
+ yield
71
+ end
72
+ end
62
73
  end
63
74
 
@@ -60,4 +60,12 @@ module Admin::AdminHelper
60
60
  @page.try(:name) || "Slices CMS"
61
61
  end
62
62
 
63
+ # Create a link to view the current page on the live site
64
+ #
65
+ # @!visibility private
66
+ def link_to_view_page(page, options = {})
67
+ link_options = { class: 'view-page', target: '_blank' }.merge(options)
68
+ path = page_path(page.path, locale: I18n.locale).gsub('//', '/')
69
+ link_to 'View page on site', path, link_options
70
+ end
63
71
  end
@@ -3,12 +3,46 @@
3
3
  module Admin::SiteMapsHelper
4
4
  include Admin::AdminHelper
5
5
 
6
+ # Returns all pages that should appear in the sitemap
7
+ #
8
+ # @!visibility private
9
+ def sitemap_pages
10
+ @sitemap_pages ||= proper_pages.only(:id, :name, :page_id, :path).group_by(&:page_id)
11
+ end
12
+
13
+ # Returns children for a given page
14
+ #
15
+ # @!visibility private
16
+ def sitemap_children_for(page)
17
+ sitemap_pages.fetch(page.id, []).sort_by(&:position)
18
+ end
19
+
20
+ # Returns criteria for pages that aren't entries
21
+ #
22
+ # @!visibility private
23
+ def proper_pages
24
+ Page.where(:_type.in => proper_types)
25
+ end
26
+
27
+ # Returns page types that aren't for entries
28
+ #
29
+ # @!visibility private
30
+ def proper_types
31
+ Page.distinct(:_type).reject { |type| type.constantize.new.entry? }
32
+ end
33
+
6
34
  # Render a list of pages
7
35
  #
8
36
  # @!visibility private
9
37
  def list_pages(pages)
10
38
  pages.inject(''.html_safe) do |html, page|
11
- html << render(partial: page_partial(page), locals: {page: page} )
39
+ html << render(
40
+ partial: page_partial(page),
41
+ locals: {
42
+ page: page,
43
+ children: sitemap_children_for(page),
44
+ }
45
+ )
12
46
  end
13
47
  end
14
48
 
@@ -93,12 +127,13 @@ module Admin::SiteMapsHelper
93
127
  link_to 'Edit Entry Template', url, class: 'button'
94
128
  end
95
129
 
96
- # Create a link to view the current page on the live site
130
+ # Return HTMl class names for a page
97
131
  #
98
132
  # @!visibility private
99
- def link_to_view_page(page)
100
- return unless page.active?
101
- link_to 'View page on site', page.path,
102
- class: 'view-page', target: '_blank'
133
+ def page_classes(page)
134
+ classes = []
135
+ classes << 'home' if page.home?
136
+ classes << page.class.name.underscore.dasherize
137
+ classes.join ' '
103
138
  end
104
139
  end
@@ -23,7 +23,7 @@ module NavigationHelper
23
23
  # @return [String]
24
24
  #
25
25
  def primary_navigation(id = 'primary_navigation')
26
- cache 'page/navigation/' + id + '/' + @page.id.to_s do
26
+ cache cache_key_for_navigation(id) do
27
27
  benchmark 'Rendered primary_navigation' do
28
28
  nav = navigation(page: Page.home, depth: 1, id: id)
29
29
  safe_concat nav
@@ -48,11 +48,12 @@ module NavigationHelper
48
48
  #
49
49
  # @param [Hash] options
50
50
  # @option options [Integer] :depth (1) Number of levels to render
51
- # @option options [Integer] :id ('secondary_navigation') Id of surrounding UL
51
+ # @option options [String] :id ('secondary_navigation') Id of surrounding UL
52
52
  # @return [String]
53
53
  #
54
54
  def secondary_navigation(options = {})
55
- cache 'page/navigation/secondary/' + @page.id.to_s do
55
+ name = options.fetch('id') { 'secondary_navigation' }
56
+ cache cache_key_for_navigation(name) do
56
57
  benchmark 'Rendered secondary_navigation' do
57
58
  secondary_ancestors = (@page.ancestors[-2] || @page).navigable_children
58
59
  nav = if secondary_ancestors.empty? || @page.home?
@@ -191,5 +192,9 @@ module NavigationHelper
191
192
  def nav_list_identifier(page)
192
193
  page.home? ? 'nav-home' : ('nav' + page.path.gsub('/', '-'))
193
194
  end
195
+
196
+ def cache_key_for_navigation(name)
197
+ ['pages', @page.id, name, Page.last_changed_at]
198
+ end
194
199
  end
195
200
 
@@ -56,8 +56,8 @@ module PagesHelper
56
56
  def edit_in_cms
57
57
  if admin_signed_in?
58
58
  links = []
59
- links << link_to("Edit #{@page.class.to_s.underscore.humanize} in CMS", admin_page_path(@page), target: '_blank')
60
- links << link_to('Edit template in CMS', admin_page_path(@page.parent, entries: 1), target: '_blank') if @page.entry?
59
+ links << link_to("Edit #{@page.class.to_s.underscore.humanize} in CMS", admin_page_path(@page, locale: I18n.locale), target: '_blank')
60
+ links << link_to('Edit template in CMS', admin_page_path(@page.parent, entries: 1, locale: I18n.locale), target: '_blank') if @page.entry?
61
61
  content = links.collect {|l| content_tag(:p, l) }.join.html_safe
62
62
 
63
63
  content_tag(:div, content, id: 'edit_in_cms')
@@ -0,0 +1,26 @@
1
+ module SnippetsHelper
2
+
3
+ # Renders a snippet on the page
4
+ #
5
+ # snippet 'address'
6
+ # # => "54B Downham Road"
7
+ #
8
+ # @param [String] key Snippet key to lookup
9
+ # @return [String]
10
+ #
11
+ def snippet(key = nil)
12
+ Snippet.find_for_key(key)
13
+ end
14
+
15
+ # Renders a localized snippet for the current locale
16
+ #
17
+ # localized_snippet 'address'
18
+ # # => "54B Downham Road"
19
+ #
20
+ # @param [String] key Snippet key to lookup
21
+ # @return [String]
22
+ #
23
+ def localized_snippet(key = nil)
24
+ snippet("#{I18n.locale}.#{key}")
25
+ end
26
+ end
data/app/models/admin.rb CHANGED
@@ -25,8 +25,7 @@ class Admin
25
25
 
26
26
  text_search_in :name, :email
27
27
 
28
- validates_uniqueness_of :email, case_sensitive: false, scope: :site_id
29
- validates_presence_of :email
28
+ validates_uniqueness_of :email, case_sensitive: false
30
29
  validates_presence_of :encrypted_password
31
30
 
32
31
  devise :database_authenticatable, :recoverable, :rememberable,
@@ -36,7 +35,7 @@ class Admin
36
35
  self.super_user == true
37
36
  end
38
37
 
39
- def as_json(options)
38
+ def as_json(options = {})
40
39
  super(options).tap do |json|
41
40
  json[:last_sign_in_at] = last_sign_in_at
42
41
 
data/app/models/asset.rb CHANGED
@@ -15,7 +15,6 @@ class Asset
15
15
  field :file_fingerprint, type: String
16
16
  field :file_dimensions, type: Hash, default: {}
17
17
 
18
- field :destroyed_at, type: Time
19
18
  field :tags, type: String
20
19
  field :page_cache, type: Array, default: []
21
20
 
@@ -34,17 +33,12 @@ class Asset
34
33
  after_file_post_process :store_dimensions, if: :is_image?
35
34
  before_save :reset_file_dimensions!, if: :file_fingerprint_changed?
36
35
  before_save :rename_file
36
+ after_destroy :remove_asset_from_pages
37
37
 
38
38
  has_and_belongs_to_many :pages
39
39
 
40
- index(
41
- [
42
- [ :destroyed_at, Mongo::ASCENDING ],
43
- [ :created_at, Mongo::DESCENDING ]
44
- ]
45
- )
46
- index :file_fingerprint
47
- index :_keywords
40
+ index({ file_fingerprint: 1 })
41
+ index({ _keywords: 1 }, { background: true })
48
42
 
49
43
  def self.make(*args)
50
44
  Slices::Asset::Maker.run(*args)
@@ -59,7 +53,7 @@ class Asset
59
53
  end
60
54
 
61
55
  def self.ordered_active
62
- where(destroyed_at: nil).desc(:created_at)
56
+ desc(:created_at)
63
57
  end
64
58
 
65
59
  def as_json(options = nil)
@@ -139,16 +133,11 @@ class Asset
139
133
  end
140
134
  end
141
135
 
142
- def soft_destroy!
143
- update_attributes!(destroyed_at: Time.now)
144
- end
145
-
146
- def soft_destroyed?
147
- destroyed_at.present?
148
- end
149
-
150
- def soft_restore!
151
- update_attributes!(destroyed_at: nil)
136
+ def remove_asset_from_pages
137
+ pages.each do |page|
138
+ page.remove_asset(self)
139
+ page.save if page.changed?
140
+ end
152
141
  end
153
142
 
154
143
  def name
@@ -1,10 +1,19 @@
1
1
  class Attachment
2
2
  include Mongoid::Document
3
+ include Slices::LocalizedFields
3
4
 
4
5
  belongs_to :asset
5
6
 
7
+ embedded_in :object, polymorphic: true
8
+
6
9
  def as_json *args
7
- attributes.as_json.merge asset: asset.as_json
10
+ result = attributes.symbolize_keys.merge asset: asset.as_json
11
+
12
+ localized_field_names.each do |name|
13
+ result.merge!(name => send(name))
14
+ end
15
+
16
+ result
8
17
  end
9
18
 
10
19
  end
data/app/models/page.rb CHANGED
@@ -9,37 +9,30 @@ class Page
9
9
  include Slices::HasAttachments::PageInstanceMethods
10
10
 
11
11
  DESCRIPTION_DEPRECATION_WARNING = "Page#description is now meta_description. If you are upgrading, run 'rake slices:migrate:meta_description' to update."
12
+ LAST_CHANGED_AT_CACHE_KEY = 'page-last-changed'
12
13
 
13
- field :name
14
+ field :name, localize: Slices::Config.i18n?
14
15
  field :role # only relevant for virtual pages
15
16
  field :active, type: Boolean, default: false
16
17
  field :layout, type: String, default: 'default'
17
- field :meta_description
18
- field :title
18
+ field :meta_description, localize: Slices::Config.i18n?
19
+ field :title, localize: Slices::Config.i18n?
19
20
  field :has_content, type: Boolean, default: false
20
21
 
21
22
  belongs_to :author, class_name: 'Admin'
22
-
23
- index :_keywords, background: true
24
-
25
23
  has_slices :slices
26
24
  has_and_belongs_to_many :assets
27
25
 
28
26
  text_search_in :introduction, :extended, :name
29
27
 
30
- scope :entries, all
28
+ scope :entries, ->{ all }
29
+ scope :virtual, ->{ where(:role.ne => nil).asc(:name) }
31
30
 
32
- def self.available_layouts
33
- Layout.all.map do |human_name, machine_name|
34
- { human_name: human_name, machine_name: machine_name }
35
- end
36
- end
37
-
38
- scope :virtual, where(:role.ne => nil).asc(:name)
39
31
  validates_presence_of :name
40
32
 
41
33
  before_save :update_has_content
42
34
  before_destroy :destroy_children
35
+ after_save :update_last_changed_at
43
36
  after_save :cache_virtual_page
44
37
 
45
38
  class NotFound < RuntimeError; end
@@ -57,10 +50,16 @@ class Page
57
50
  end
58
51
  end
59
52
 
60
- # Virtual pages don't live in the and are not associated with a
61
- # specific URL. Instead, they can be rendered at any path, depending
62
- # on the circumstances (e.g. when a page isn't found, or when an error
63
- # occurs). Consequently they aren't created with a :parent attribute.
53
+ def self.available_layouts
54
+ Layout.all.map do |human_name, machine_name|
55
+ { human_name: human_name, machine_name: machine_name }
56
+ end
57
+ end
58
+
59
+ # Virtual pages are not associated with a specific URL. Instead, they
60
+ # can be rendered at any path, depending on the circumstances (e.g.
61
+ # when a page isn't found, or when an error occurs). Consequently they
62
+ # aren't created with a :parent attribute.
64
63
  def self.make(attributes = {})
65
64
  attributes = attributes.symbolize_keys
66
65
  parent = parent_from_attributes(attributes)
@@ -78,17 +77,27 @@ class Page
78
77
  end
79
78
 
80
79
  def self.find_by_id(id)
80
+ ActiveSupport::Deprecation::warn 'Page.find_by_id is depreciated, please use Page.find instead.'
81
81
  find(id)
82
- rescue BSON::InvalidObjectId, Mongoid::Errors::DocumentNotFound
82
+ rescue Mongoid::Errors::DocumentNotFound
83
83
  nil
84
84
  end
85
85
 
86
86
  def self.find_by_id!(id)
87
+ ActiveSupport::Deprecation::warn 'Page.find_by_id is depreciated, please use Page.find instead.'
87
88
  find_by_id(id) || (raise NotFound)
88
89
  end
89
90
 
90
91
  def self.find_virtual(role)
91
- first(conditions: { role: role })
92
+ find_by(role: role)
93
+ end
94
+
95
+ def update_last_changed_at
96
+ Rails.cache.write(LAST_CHANGED_AT_CACHE_KEY, Time.now.to_i)
97
+ end
98
+
99
+ def self.last_changed_at
100
+ Rails.cache.read(LAST_CHANGED_AT_CACHE_KEY) || 0
92
101
  end
93
102
 
94
103
  def cacheable_virtual_page?
@@ -177,23 +186,25 @@ class Page
177
186
  self.meta_description = value
178
187
  end
179
188
 
180
- private
181
- def self.parent_from_attributes(attributes)
182
- if attributes.has_key?(:parent_path)
183
- Page.find_by_path(attributes.delete(:parent_path))
184
- elsif attributes.has_key?(:parent_id)
185
- Page.find_by_id(attributes.delete(:parent_id))
186
- else
187
- attributes.delete(:parent)
188
- end
189
+ def self.parent_from_attributes(attributes)
190
+ if attributes.has_key?(:parent_path)
191
+ Page.find_by_path(attributes.delete(:parent_path))
192
+ elsif attributes.has_key?(:parent_id)
193
+ Page.find(attributes.delete(:parent_id))
194
+ else
195
+ attributes.delete(:parent)
189
196
  end
197
+ end
198
+ private_class_method :parent_from_attributes
190
199
 
191
- def self.page_exists?(path)
192
- Page.find_by_path(path)
193
- rescue NotFound
194
- false
195
- end
200
+ def self.page_exists?(path)
201
+ Page.find_by_path(path)
202
+ rescue NotFound
203
+ false
204
+ end
205
+ private_class_method :page_exists?
196
206
 
207
+ private
197
208
  def update_has_content
198
209
  self.has_content = slices.any?
199
210
  true # must be true otherwise save will fail