diff --git a/.rspec b/.rspec index f5f59fd4..616c4338 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,2 @@ ---drb --colour +--backtrace diff --git a/app/models/locomotive/extensions/page/tree.rb b/app/models/locomotive/extensions/page/tree.rb index d0d9a254..ed308961 100644 --- a/app/models/locomotive/extensions/page/tree.rb +++ b/app/models/locomotive/extensions/page/tree.rb @@ -9,6 +9,13 @@ module Locomotive include ::Mongoid::Tree include ::Mongoid::Tree::Ordering + ## fields ## + field :depth, :type => Integer, :default => 0 + + ## callbacks ## + before_save :persist_depth + before_destroy :delete_descendants + ## indexes ## index :position index [:depth.asc, :position.asc] @@ -88,6 +95,16 @@ module Locomotive end end + def depth + self.parent_ids.count + end + + protected + + def persist_depth + self.attributes[:depth] = self.depth + end + end end end diff --git a/app/models/locomotive/page.rb b/app/models/locomotive/page.rb index 337df736..2bb23763 100644 --- a/app/models/locomotive/page.rb +++ b/app/models/locomotive/page.rb @@ -42,7 +42,7 @@ module Locomotive validates_presence_of :site, :title, :slug validates_uniqueness_of :slug, :scope => [:site_id, :parent_id] validates_uniqueness_of :handle, :allow_blank => true - validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 } + validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth <= 1 } ## named scopes ## scope :latest_updated, :order_by => [[:updated_at, :desc]], :limit => Locomotive.config.ui.latest_entries_nb diff --git a/app/models/locomotive/site.rb b/app/models/locomotive/site.rb index d51237ff..30d9450b 100644 --- a/app/models/locomotive/site.rb +++ b/app/models/locomotive/site.rb @@ -67,8 +67,7 @@ module Locomotive :slug => slug, :title => ::I18n.t("attributes.defaults.pages.#{slug}.title"), :raw_template => ::I18n.t("attributes.defaults.pages.#{slug}.body"), - :published => true, - :depth => 0 + :published => true }) end end diff --git a/lib/locomotive/configuration.rb b/lib/locomotive/configuration.rb index 19b883bd..512a5364 100644 --- a/lib/locomotive/configuration.rb +++ b/lib/locomotive/configuration.rb @@ -6,7 +6,7 @@ module Locomotive :domain => 'example.com', :reserved_subdomains => %w{www admin email blog webmail mail support help site sites}, # :forbidden_paths => %w{layouts snippets stylesheets javascripts assets admin system api}, - :reserved_slugs => %w{stylesheets javascripts assets admin images api pages edit}, + :reserved_slugs => %w{stylesheets javascripts assets admin locomotive images api pages edit}, :locales => %w{en de fr pt-BR it nl no es ru}, :site_locales => %w{en de fr pt-BR it nl no es ru}, :cookie_key => '_locomotive_session', diff --git a/lib/locomotive/mongoid/patches.rb b/lib/locomotive/mongoid/patches.rb index 3859ac8b..ec0326fe 100644 --- a/lib/locomotive/mongoid/patches.rb +++ b/lib/locomotive/mongoid/patches.rb @@ -31,4 +31,45 @@ module Mongoid#:nodoc: end end end -end \ No newline at end of file + + # make the validators work with localized field + module Validations #:nodoc: + + def read_attribute_for_validation_with_localization(attr) + if fields[attr.to_s] && fields[attr.to_s].localized? + send(attr.to_sym) + else + read_attribute_for_validation_without_localization(attr) + end + end + + alias_method_chain :read_attribute_for_validation, :localization + + class PresenceValidator < ActiveModel::EachValidator + + def validate_each(document, attribute, value) + document.errors.add(attribute, :blank, options) if value.blank? + end + end + + end + # + # class UniquenessValidator < ActiveModel::EachValidator + # + # protected + # + # def criterion_with_localization(document, attribute, value) + # field = document.fields[attribute.to_s] + # if field && field.localized? && !value.blank? + # value = document.send(attribute.to_sym) + # end + # + # criterion_without_localization(document, attribute, value) + # end + # + # alias_method_chain :criterion, :localization + # + # end + # + # end +end diff --git a/spec/cells/locomotive/global_actions_spec.rb b/spec/cells/locomotive/global_actions_spec.rb index 3d3ed1e7..1ef91ecc 100644 --- a/spec/cells/locomotive/global_actions_spec.rb +++ b/spec/cells/locomotive/global_actions_spec.rb @@ -15,7 +15,6 @@ describe Locomotive::GlobalActionsCell do end it 'has a link to edit my account' do - puts "last test" menu.should have_link('Admin') end diff --git a/spec/models/locomotive/page_spec.rb b/spec/models/locomotive/page_spec.rb index 7abfada5..42774b49 100644 --- a/spec/models/locomotive/page_spec.rb +++ b/spec/models/locomotive/page_spec.rb @@ -13,45 +13,69 @@ describe Locomotive::Page do FactoryGirl.build(:page).should be_valid end - # Validations ## + describe 'validation' do - %w{site title}.each do |field| - it "should validate presence of #{field}" do - page = FactoryGirl.build(:page, field.to_sym => nil) - page.should_not be_valid - page.errors[field.to_sym].should == ["can't be blank"] + %w{site title}.each do |field| + it "requires the presence of the #{field}" do + page = FactoryGirl.build(:page, field.to_sym => nil) + page.should_not be_valid + page.errors[field.to_sym].should == ["can't be blank"] + end end - end - it 'should validate presence of slug' do - page = FactoryGirl.build(:page, :title => nil, :slug => nil) - page.should_not be_valid - page.errors[:slug].should == ["can't be blank"] - end - - it 'should validate uniqueness of slug' do - page = FactoryGirl.create(:page) - (page = FactoryGirl.build(:page, :site => page.site)).should_not be_valid - page.errors[:slug].should == ["is already taken"] - end - - it 'should validate uniqueness of slug within a "folder"' do - site = FactoryGirl.create(:site) - root = FactoryGirl.create(:page, :slug => 'index', :site => site) - child_1 = FactoryGirl.create(:page, :slug => 'first_child', :parent => root, :site => site) - (page = FactoryGirl.build(:page, :slug => 'first_child', :parent => root, :site => site)).should_not be_valid - page.errors[:slug].should == ["is already taken"] - - page.slug = 'index' - page.valid?.should be_true - end - - %w{admin stylesheets images javascripts}.each do |slug| - it "should consider '#{slug}' as invalid" do - page = FactoryGirl.build(:page, :slug => slug) + it 'requires the presence of the slug' do + page = FactoryGirl.build(:page, :title => nil, :slug => nil) page.should_not be_valid - page.errors[:slug].should == ["is reserved"] + page.errors[:slug].should == ["can't be blank"] end + + it 'requires the uniqueness of the slug' do + page = FactoryGirl.create(:page) + another_page = FactoryGirl.build(:page, :site => page.site) + another_page.should_not be_valid + another_page.errors[:slug].should == ["is already taken"] + end + + it 'requires the uniqueness of the slug within a "folder"' do + site = FactoryGirl.create(:site) + root = FactoryGirl.create(:page, :slug => 'index', :site => site) + child_1 = FactoryGirl.create(:page, :slug => 'first_child', :parent => root, :site => site) + (page = FactoryGirl.build(:page, :slug => 'first_child', :parent => root, :site => site)).should_not be_valid + page.errors[:slug].should == ["is already taken"] + + page.slug = 'index' + page.valid?.should be_true + end + + %w{admin locomotive stylesheets images javascripts}.each do |slug| + it "considers '#{slug}' as invalid" do + page = FactoryGirl.build(:page, :slug => slug) + page.stubs(:depth).returns(1) + page.should_not be_valid + page.errors[:slug].should == ["is reserved"] + end + end + + context '#i18n' do + + before(:each) do + ::Mongoid::Fields::I18n.locale = 'en' + @page = FactoryGirl.build(:page, :title => 'Hello world') + ::Mongoid::Fields::I18n.locale = 'fr' + end + + after(:all) do + ::Mongoid::Fields::I18n.locale = 'en' + end + + it 'requires the presence of the title' do + @page.title = '' + @page.valid?.should be_false + @page.errors[:title].should == ["can't be blank"] + end + + end + end # Named scopes ## @@ -64,7 +88,9 @@ describe Locomotive::Page do it 'should tell if the page is the index one' do FactoryGirl.build(:page, :slug => 'index', :site => nil).index?.should be_true - FactoryGirl.build(:page, :slug => 'index', :depth => 1, :site => nil).index?.should be_false + page = FactoryGirl.build(:page, :slug => 'index', :site => nil) + page.stubs(:depth).returns(1) + page.index?.should be_false end it 'should have normalized slug' do @@ -108,44 +134,42 @@ describe Locomotive::Page do end - describe 'acts as tree' do + describe 'tree organization' do before(:each) do @home = FactoryGirl.create(:page) @child_1 = FactoryGirl.create(:page, :title => 'Subpage 1', :slug => 'foo', :parent_id => @home._id, :site => @home.site) end - it 'should add root elements' do + it 'adds root elements' do page_404 = FactoryGirl.create(:page, :title => 'Page not found', :slug => '404', :site => @home.site) Locomotive::Page.roots.count.should == 2 Locomotive::Page.roots.should == [@home, page_404] end - it 'should add sub pages' do + it 'adds sub pages' do child_2 = FactoryGirl.create(:page, :title => 'Subpage 2', :slug => 'bar', :parent => @home, :site => @home.site) @home = Locomotive::Page.find(@home.id) @home.children.count.should == 2 @home.children.should == [@child_1, child_2] end - it 'should move its children accordingly' do + it 'moves its children accordingly' do sub_child_1 = FactoryGirl.create(:page, :title => 'Sub Subpage 1', :slug => 'bar', :parent => @child_1, :site => @home.site) - archives = FactoryGirl.create(:page, :title => 'archives', :slug => 'archives', :parent => @home, :site => @home.site) - posts = FactoryGirl.create(:page, :title => 'posts', :slug => 'posts', :parent => archives, :site => @home.site) + archives = FactoryGirl.create(:page, :title => 'archives', :slug => 'archives', :parent => @home, :site => @home.site) + posts = FactoryGirl.create(:page, :title => 'posts', :slug => 'posts', :parent => archives, :site => @home.site) - @child_1.parent_id = archives._id + @child_1.parent = archives @child_1.save - @child_1.position.should == 2 @home.reload.children.count.should == 1 archives.reload.children.count.should == 2 archives.children.last.depth.should == 2 - archives.children.last.position.should == 2 archives.children.last.children.first.depth.should == 3 end - it 'should destroy descendants as well' do + it 'destroys descendants as well' do FactoryGirl.create(:page, :title => 'Sub Subpage 1', :slug => 'bar', :parent_id => @child_1._id, :site => @home.site) @child_1.destroy Locomotive::Page.where(:slug => 'bar').first.should be_nil @@ -163,12 +187,12 @@ describe Locomotive::Page do 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 } + [@child_1, @child_2, @child_3].each_with_index { |c, i| c.position.should == i } 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 } + [@child_1, @child_3.reload].each_with_index { |c, i| c.position.should == i } end end @@ -176,8 +200,9 @@ describe Locomotive::Page do describe 'templatized extension' do before(:each) do - @page = FactoryGirl.build(:page, :site => nil, :templatized => true, :content_type_id => 42) - Locomotive::ContentType.stubs(:find).returns(FactoryGirl.build(:content_type, :site => nil)) + @page = FactoryGirl.build(:page, :templatized => true, :target_klass_name => 'Foo') + # @page.stubs(:target_klass) + # Locomotive::ContentType.stubs(:find).returns(FactoryGirl.build(:content_type, :site => nil)) end it 'is considered as a templatized page' do @@ -189,8 +214,52 @@ describe Locomotive::Page do @page.slug.should == 'content_type_template' end - it 'does forget to set the content type id' do - @page.content_type_id.should == 42 + it 'returns the target klass' do + @page.target_klass.should == Foo + end + + it 'has a name for the target entry' do + @page.target_entry_name.should == 'foo' + end + + it 'uses the find_by_permalink method when fetching the entry' do + Foo.expects(:find_by_permalink) + @page.fetch_target_entry('foo') + end + + context 'using a content type' do + + before(:each) do + @site = FactoryGirl.build(:site) + @content_type = FactoryGirl.build(:content_type, :slug => 'posts', :site => @site) + @page.site = @site + @page.target_klass_name = 'Locomotive::Entry42' + end + + it 'has a name for the target entry' do + @site.stubs(:content_types).returns(mock(:find => @content_type)) + @page.target_entry_name.should == 'post' + end + + context '#security' do + + before(:each) do + Locomotive::ContentType.stubs(:find).returns(@content_type) + end + + it 'is valid if the content type belongs to the site' do + @page.send(:ensure_target_klass_name_security) + @page.errors.should be_empty + end + + it 'does not valid the page if the content type does not belong to the site' do + @content_type.site = FactoryGirl.build(:site) + @page.send(:ensure_target_klass_name_security) + @page.errors[:target_klass_name].should == ['presents a security problem'] + end + + end + end end @@ -198,12 +267,12 @@ describe Locomotive::Page do describe 'listed extension' do it 'is considered as a visible page' do - @page = FactoryGirl.build(:page, :site => nil, :content_type_id => 42) + @page = FactoryGirl.build(:page, :site => nil) @page.listed?.should be_true end it 'is not considered as a visible page' do - @page = FactoryGirl.build(:page, :site => nil, :listed => false, :content_type_id => 42) + @page = FactoryGirl.build(:page, :site => nil, :listed => false) @page.listed?.should be_false end @@ -231,4 +300,14 @@ describe Locomotive::Page do @page.errors[:redirect_url].should == ["is invalid"] end end + + class Foo + end + + class Locomotive::Entry42 + end + + def fake_bson_id(id) + BSON::ObjectId(id.to_s.rjust(24, '0')) + end end