page acts as tree / list + improve validations
This commit is contained in:
parent
e81b4a353a
commit
ae20b51636
3
Gemfile
3
Gemfile
@ -5,13 +5,14 @@ source "http://gems.github.com"
|
|||||||
gem "rails", "3.0.0.beta3"
|
gem "rails", "3.0.0.beta3"
|
||||||
|
|
||||||
gem "liquid"
|
gem "liquid"
|
||||||
gem "bson_ext"
|
gem "bson_ext", '0.20.1'
|
||||||
gem "mongo_ext"
|
gem "mongo_ext"
|
||||||
gem "mongoid", ">= 2.0.0.beta2"
|
gem "mongoid", ">= 2.0.0.beta2"
|
||||||
gem "warden"
|
gem "warden"
|
||||||
gem "devise", ">= 1.1.rc0"
|
gem "devise", ">= 1.1.rc0"
|
||||||
gem "haml", '>= 3.0.0.beta.2', :git => 'git://github.com/nex3/haml.git'
|
gem "haml", '>= 3.0.0.beta.2', :git => 'git://github.com/nex3/haml.git'
|
||||||
gem "formtastic", :git => 'git://github.com/justinfrench/formtastic.git', :branch => 'rails3'
|
gem "formtastic", :git => 'git://github.com/justinfrench/formtastic.git', :branch => 'rails3'
|
||||||
|
gem "mongoid_acts_as_tree", :git => 'git://github.com/evansagge/mongoid_acts_as_tree.git'
|
||||||
|
|
||||||
|
|
||||||
# Development environment
|
# Development environment
|
||||||
|
@ -1,37 +1,67 @@
|
|||||||
class Page
|
class Page
|
||||||
include Mongoid::Document
|
include Mongoid::Document
|
||||||
include Mongoid::Timestamps
|
include Mongoid::Timestamps
|
||||||
|
include Mongoid::Acts::Tree
|
||||||
|
|
||||||
## fields ##
|
## fields ##
|
||||||
field :title
|
field :title
|
||||||
field :path
|
field :slug
|
||||||
field :published, :type => Boolean, :default => false
|
field :published, :type => Boolean, :default => false
|
||||||
field :keywords
|
field :keywords
|
||||||
field :description
|
field :description
|
||||||
|
field :position, :type => Integer
|
||||||
|
|
||||||
## associations ##
|
## associations ##
|
||||||
belongs_to_related :site
|
belongs_to_related :site
|
||||||
embeds_many :parts, :class_name => 'PagePart'
|
embeds_many :parts, :class_name => 'PagePart'
|
||||||
|
|
||||||
## validations ##
|
## validations ##
|
||||||
validates_presence_of :site, :title, :path
|
validates_presence_of :site, :title, :slug
|
||||||
validates_uniqueness_of :path, :scope => :site_id
|
validates_uniqueness_of :slug, :scope => :site_id
|
||||||
validate :path_must_not_begin_with_reserverd_keywords
|
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
|
||||||
|
|
||||||
## callbacks ##
|
## callbacks ##
|
||||||
|
before_create :add_to_list_bottom
|
||||||
before_create :add_body_part
|
before_create :add_body_part
|
||||||
|
before_destroy :remove_from_list
|
||||||
|
before_validate :normalize_slug
|
||||||
|
|
||||||
## named scopes ##
|
## named scopes ##
|
||||||
|
|
||||||
|
## behaviours ##
|
||||||
|
acts_as_tree
|
||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
def add_body_part
|
def add_body_part
|
||||||
self.parts.build :name => 'body', :value => '---body here---'
|
self.parts.build :name => 'body', :value => '---body here---'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parent=(owner) # missing in acts_as_tree
|
||||||
|
self[self.parent_id_field] = owner._id
|
||||||
|
self[self.path_field] = owner[owner.path_field] + [owner._id]
|
||||||
|
self[self.depth_field] = owner[owner.depth_field] + 1
|
||||||
|
self.instance_variable_set :@_will_move, true
|
||||||
|
end
|
||||||
|
|
||||||
def path_must_not_begin_with_reserverd_keywords
|
def route
|
||||||
if (self.path =~ /^(#{Locomotive.config.forbidden_paths.join('|')})\//) == 0
|
File.join self.self_and_ancestors.map(&:slug)
|
||||||
errors.add(:path, :reserved_keywords)
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def add_to_list_bottom
|
||||||
|
self.position = (Page.where(:_id.ne => self._id).and(:parent_id => self.parent_id).max(:position) || 0) + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_from_list
|
||||||
|
Page.where(:parent_id => self.parent_id).and(:position.gt => self.position).each do |p|
|
||||||
|
p.position -= 1
|
||||||
|
p.save
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def normalize_slug
|
||||||
|
self.slug.slugify!(:without_extension => true) if self.slug.present?
|
||||||
|
end
|
||||||
end
|
end
|
4
doc/TODO
4
doc/TODO
@ -3,6 +3,10 @@
|
|||||||
- localize devise emails
|
- localize devise emails
|
||||||
x admin layout
|
x admin layout
|
||||||
x logout button
|
x logout button
|
||||||
|
x slugify page
|
||||||
|
x validation page slug
|
||||||
|
- sitemap
|
||||||
- pages section (CRUD)
|
- pages section (CRUD)
|
||||||
- my account section (part of settings)
|
- my account section (part of settings)
|
||||||
- snippets section
|
- snippets section
|
||||||
|
- layouts section
|
||||||
|
@ -21,4 +21,8 @@ class String
|
|||||||
s
|
s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def slugify!(options = {})
|
||||||
|
replace(self.slugify(options))
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -6,7 +6,8 @@ module Locomotive
|
|||||||
:name => 'LocomotiveApp',
|
:name => 'LocomotiveApp',
|
||||||
:default_domain => 'rails.local.fr',
|
:default_domain => 'rails.local.fr',
|
||||||
:reserved_subdomains => %w{www admin email blog webmail mail support help site sites},
|
:reserved_subdomains => %w{www admin email blog webmail mail support help site sites},
|
||||||
:forbidden_paths => %w{layouts snippets stylesheets javascripts assets admin system api}
|
# :forbidden_paths => %w{layouts snippets stylesheets javascripts assets admin system api},
|
||||||
|
:reserved_slugs => %w{stylesheets javascripts assets admin images api pages}
|
||||||
}
|
}
|
||||||
|
|
||||||
cattr_accessor :settings
|
cattr_accessor :settings
|
||||||
|
29
lib/locomotive/sitemap.rb
Normal file
29
lib/locomotive/sitemap.rb
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
module Locomotive
|
||||||
|
|
||||||
|
module SiteMap
|
||||||
|
|
||||||
|
def self.build(pages)
|
||||||
|
return Folder.new if pages.empty?
|
||||||
|
|
||||||
|
pages = pages.to_a.clone # make a secure copy
|
||||||
|
|
||||||
|
root = Folder.new :root => pages.delete_if { |p| p.path == 'index' }
|
||||||
|
|
||||||
|
pages.each do |page|
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Folder
|
||||||
|
attr_accessor :root, :name, :children
|
||||||
|
|
||||||
|
def initialize(attributes = {})
|
||||||
|
self.root = attributes[:root]
|
||||||
|
self.name = attributes[:name]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -18,5 +18,5 @@ end
|
|||||||
Factory.define :page do |p|
|
Factory.define :page do |p|
|
||||||
p.association :site, :factory => :site
|
p.association :site, :factory => :site
|
||||||
p.title 'Home page'
|
p.title 'Home page'
|
||||||
p.path 'index'
|
p.slug 'index'
|
||||||
end
|
end
|
@ -8,7 +8,7 @@ describe Page do
|
|||||||
|
|
||||||
## Validations ##
|
## Validations ##
|
||||||
|
|
||||||
%w{site title path}.each do |field|
|
%w{site title slug}.each do |field|
|
||||||
it "should validate presence of #{field}" do
|
it "should validate presence of #{field}" do
|
||||||
page = Factory.build(:page, field.to_sym => nil)
|
page = Factory.build(:page, field.to_sym => nil)
|
||||||
page.should_not be_valid
|
page.should_not be_valid
|
||||||
@ -16,10 +16,18 @@ describe Page do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should validate uniqueness of path' do
|
it 'should validate uniqueness of slug' do
|
||||||
page = Factory(:page)
|
page = Factory(:page)
|
||||||
(page = Factory.build(:page, :site => page.site)).should_not be_valid
|
(page = Factory.build(:page, :site => page.site)).should_not be_valid
|
||||||
page.errors[:path].should == ["is already taken"]
|
page.errors[:slug].should == ["is already taken"]
|
||||||
|
end
|
||||||
|
|
||||||
|
%w{admin stylesheets images javascripts}.each do |slug|
|
||||||
|
it "should consider '#{slug}' as invalid" do
|
||||||
|
page = Factory.build(:page, :slug => slug)
|
||||||
|
page.should_not be_valid
|
||||||
|
page.errors[:slug].should == ["is reserved"]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
## Named scopes ##
|
## Named scopes ##
|
||||||
@ -36,7 +44,59 @@ describe Page do
|
|||||||
page.parts.first.name.should == 'body'
|
page.parts.first.name.should == 'body'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should have normalized slug' do
|
||||||
|
page = Factory.build(:page, :slug => ' Valid ité.html ')
|
||||||
|
page.valid?
|
||||||
|
page.slug.should == 'Valid_ite'
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'acts as tree' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@home = Factory(:page)
|
||||||
|
@child_1 = Factory(:page, :title => 'Subpage 1', :slug => 'foo', :parent => @home, :site => @home.site)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add root elements' do
|
||||||
|
page_404 = Factory(:page, :title => 'Page not found', :slug => '404', :site => @home.site)
|
||||||
|
Page.roots.count.should == 2
|
||||||
|
Page.roots.should == [@home, page_404]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should add sub pages' do
|
||||||
|
child_2 = Factory(:page, :title => 'Subpage 2', :slug => 'bar', :parent => @home, :site => @home.site)
|
||||||
|
Page.first.children.count.should == 2
|
||||||
|
Page.first.children.should == [@child_1, child_2]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should generate a route from parents' do
|
||||||
|
nested_page = Factory(:page, :title => 'Sub sub page 1', :slug => 'bar', :parent => @child_1, :site => @home.site)
|
||||||
|
nested_page.route.should == 'index/foo/bar'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'acts as list' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@home = Factory(:page)
|
||||||
|
@child_1 = Factory(:page, :title => 'Subpage 1', :slug => 'foo', :parent => @home, :site => @home.site)
|
||||||
|
@child_2 = Factory(:page, :title => 'Subpage 2', :slug => 'bar', :parent => @home, :site => @home.site)
|
||||||
|
@child_3 = Factory(:page, :title => 'Subpage 3', :slug => 'acme', :parent => @home, :site => @home.site)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should be at the bottom of the folder once created' do
|
||||||
|
[@child_1, @child_2, @child_3].each_with_index { |c, i| c.position.should == i + 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should have its position updated if a sibling is removed' do
|
||||||
|
@child_2.destroy
|
||||||
|
[@child_1, @child_3.reload].each_with_index { |c, i| c.position.should == i + 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
Loading…
Reference in New Issue
Block a user