completely get rid of old layouts + enhance snippets integration + refactor parsing page module

This commit is contained in:
dinedine 2010-08-22 00:48:24 +02:00
parent dbc542c4d7
commit 9d78a32340
24 changed files with 151 additions and 246 deletions

View File

@ -0,0 +1,40 @@
module Models
module Extensions
module Page
module Parse
extend ActiveSupport::Concern
included do
field :serialized_template, :type => Binary
before_validation :serialize_template
end
module InstanceMethods
def template
@template ||= Marshal.load(read_attribute(:serialized_template).to_s) rescue nil
end
protected
def serialize_template
if self.new_record? || self.raw_template_changed?
begin
@template = ::Liquid::Template.parse(self.raw_template, { :site => self.site })
@template.root.context.clear
self.serialized_template = BSON::Binary.new(Marshal.dump(@template))
rescue ::Liquid::SyntaxError => error
self.errors.add :template, :liquid_syntax_error
end
end
end
end
end
end
end
end

View File

@ -1,32 +0,0 @@
class LiquidTemplate
include Locomotive::Mongoid::Document
## fields ##
field :name
field :slug
field :value
## associations ##
referenced_in :site
## callbacks ##
before_validation :normalize_slug
## validations ##
validates_presence_of :site, :name, :slug, :value
validates_uniqueness_of :slug, :scope => :site_id
## behaviours ##
liquify_template :value
## methods ##
protected
def normalize_slug
self.slug = self.name.clone if self.slug.blank? && self.name.present?
self.slug.slugify!(:without_extension => true, :downcase => true) if self.slug.present?
end
end

View File

@ -5,6 +5,7 @@ class Page
## Extensions ## ## Extensions ##
include Models::Extensions::Page::Tree include Models::Extensions::Page::Tree
# include Models::Extensions::Page::Parts # include Models::Extensions::Page::Parts
include Models::Extensions::Page::Parse
include Models::Extensions::Page::Render include Models::Extensions::Page::Render
include Models::Extensions::Page::Templatized include Models::Extensions::Page::Templatized
@ -12,11 +13,10 @@ class Page
field :title field :title
field :slug field :slug
field :fullpath field :fullpath
field :raw_template
field :published, :type => Boolean, :default => false field :published, :type => Boolean, :default => false
field :cache_strategy, :default => 'none' field :cache_strategy, :default => 'none'
field :layout_template # FIXME: added for liquid inheritance
## associations ## ## associations ##
referenced_in :site referenced_in :site
@ -36,10 +36,6 @@ class Page
scope :not_found, :where => { :slug => '404', :depth => 0 } scope :not_found, :where => { :slug => '404', :depth => 0 }
scope :published, :where => { :published => true } scope :published, :where => { :published => true }
## behaviours ##
# liquify_template :joined_parts
liquify_template :layout_template
## methods ## ## methods ##
def index? def index?

View File

@ -83,7 +83,7 @@ class Site
self.pages.create({ self.pages.create({
:slug => slug, :slug => slug,
:title => I18n.t("attributes.defaults.pages.#{slug}.title"), :title => I18n.t("attributes.defaults.pages.#{slug}.title"),
:layout_template => I18n.t("attributes.defaults.pages.#{slug}.body"), :raw_template => I18n.t("attributes.defaults.pages.#{slug}.body"),
:published => true :published => true
}) })
end end

View File

@ -1,3 +1,30 @@
class Snippet < LiquidTemplate class Snippet
include Locomotive::Mongoid::Document
## fields ##
field :name
field :slug
field :template
## associations ##
referenced_in :site
## callbacks ##
before_validation :normalize_slug
## validations ##
validates_presence_of :site, :name, :slug, :template
validates_uniqueness_of :slug, :scope => :site_id
## methods ##
protected
def normalize_slug
self.slug = self.name.clone if self.slug.blank? && self.name.present?
self.slug.slugify!(:without_extension => true, :downcase => true) if self.slug.present?
end
end end

View File

@ -21,11 +21,11 @@
= f.input :cache_strategy, :as => :select, :collection => options_for_page_cache_strategy, :include_blank => false = f.input :cache_strategy, :as => :select, :collection => options_for_page_cache_strategy, :include_blank => false
= f.foldable_inputs :name => :layout_template do = f.foldable_inputs :name => :raw_template do
= f.custom_input :value, :css => 'code full', :with_label => false do = f.custom_input :value, :css => 'code full', :with_label => false do
= f.label :layout_template = f.label :raw_template
%code{ :class => 'html' } %code{ :class => 'html' }
= f.text_area :layout_template = f.text_area :raw_template
/ .more / .more
/ = link_to t('admin.image_picker.link'), admin_theme_assets_path, :id => 'image-picker-link' / = link_to t('admin.image_picker.link'), admin_theme_assets_path, :id => 'image-picker-link'

View File

@ -7,8 +7,8 @@
= f.input :slug, :required => false = f.input :slug, :required => false
= f.inputs :name => :code do = f.inputs :name => :code do
= f.custom_input :value, :css => 'full', :with_label => false do = f.custom_input :template, :css => 'full', :with_label => false do
%code{ :class => 'html' } %code{ :class => 'html' }
= f.text_area :value = f.text_area :template
.more .more
= link_to t('admin.image_picker.link'), admin_theme_assets_path, :id => 'image-picker-link' = link_to t('admin.image_picker.link'), admin_theme_assets_path, :id => 'image-picker-link'

View File

@ -245,7 +245,7 @@ en:
information: General information information: General information
meta: SEO Metadata meta: SEO Metadata
code: Code code: Code
layout_template: Layout raw_template: Layout
credentials: Credentials credentials: Credentials
language: Language language: Language
sites: Sites sites: Sites

View File

@ -244,7 +244,7 @@ fr:
information: Informations générales information: Informations générales
meta: SEO Metadata meta: SEO Metadata
code: Code code: Code
layout_template: Gabarit raw_template: Gabarit
credentials: Informations de connexion credentials: Informations de connexion
language: Langue language: Langue
sites: Sites sites: Sites

View File

@ -1,4 +1,4 @@
Feature: Engine Feature: Inheritance
As a designer As a designer
I want to be able to build more complex page html layouts I want to be able to build more complex page html layouts
with shared template code with shared template code

View File

@ -0,0 +1,21 @@
Feature: Snippets
As a designer
I want to insert re-usable piece of code among pages
Background:
Given I have the site: "test site" set up
And a snippet named "yield" with the template:
"""
HELLO WORLD !
"""
Scenario: Simple include
Given a page named "hello-world" with the template:
"""
My application says {% include 'yield' %}
"""
When I view the rendered page at "/hello-world"
Then the rendered output should look like:
"""
My application says HELLO WORLD !
"""

View File

@ -27,13 +27,3 @@ end
### Common ### Common
# def create_layout_samples
# Factory(:layout, :site => @site, :name => 'One column', :value => %{<html>
# <head>
# <title>My website</title>
# </head>
# <body>
# <div id="main">{{ content_for_layout }}</div>
# </body>
# </html>})
# end

View File

@ -1,9 +1,9 @@
### Pages ### Pages
# helps create a simple content page (parent: "index") with a slug, contents, and layout # helps create a simple content page (parent: "index") with a slug, contents, and template
def create_content_page(page_slug, page_contents, template = nil) def create_content_page(page_slug, page_contents, template = nil)
@home = @site.pages.where(:slug => "index").first || Factory(:page) @home = @site.pages.where(:slug => "index").first || Factory(:page)
page = @site.pages.create(:slug => page_slug, :body => page_contents, :parent => @home, :title => "some title", :published => true, :layout_template => template) page = @site.pages.create(:slug => page_slug, :body => page_contents, :parent => @home, :title => "some title", :published => true, :raw_template => template)
page.should be_valid page.should be_valid
page page
end end
@ -46,7 +46,7 @@ Then /^I should have "(.*)" in the (.*) page$/ do |content, page_slug|
page = @site.pages.where(:slug => page_slug).first page = @site.pages.where(:slug => page_slug).first
raise "Could not find page: #{page_slug}" unless page raise "Could not find page: #{page_slug}" unless page
page.layout_template.should == content page.raw_template.should == content
end end
# checks if the rendered body matches a string # checks if the rendered body matches a string

View File

@ -0,0 +1,16 @@
### Snippets
# helps create a simple snippet with a slug and template
def create_snippet(name, template = nil)
snippet = @site.snippets.create(:name => name, :template => template)
snippet.should be_valid
snippet
end
# creates a snippet
Given /^a snippet named "([^"]*)" with the template:$/ do |name, template|
@snippet = create_snippet(name, template)
end

View File

@ -1,5 +1,3 @@
%w{. tags drops filters}.each do |dir| %w{. tags drops filters}.each do |dir|
Dir[File.join(File.dirname(__FILE__), 'liquid', dir, '*.rb')].each { |lib| require lib } Dir[File.join(File.dirname(__FILE__), 'liquid', dir, '*.rb')].each { |lib| require lib }
end end
::Liquid::Template.file_system = Locomotive::Liquid::DbFileSystem.new # enable snippets

View File

@ -1,18 +0,0 @@
module Locomotive
module Liquid
class DbFileSystem
# Works only with snippets
def read_template_file(site, template_path)
raise FileSystemError, "Illegal snippet name '#{template_path}'" unless template_path =~ /^[^.\/][a-zA-Z0-9_\/]+$/
snippet = site.snippets.where(:slug => template_path).first
raise FileSystemError, "No such snippet '#{template_path}'" if snippet.nil?
snippet.template
end
end
end
end

View File

@ -1,66 +0,0 @@
module Locomotive
module Liquid
module LiquifyTemplate
def self.included(base)
base.extend(ClassMethods)
end
# Store the parsed version of a liquid template into a column in order to increase performance
# See http://cjohansen.no/en/rails/liquid_email_templates_in_rails
#
# class Page
# liquify_template :body
# end
#
# page = Page.new :body => '...some liquid tags'
# page.template # Liquid::Template
#
#
module ClassMethods
def liquify_template(source = :value)
field :serialized_template, :type => Binary
before_validation :store_template
class_eval <<-EOV
def liquify_template_source
self.send(:#{source.to_s})
end
EOV
include InstanceMethods
end
end
module InstanceMethods
def template
@template ||= Marshal.load(read_attribute(:serialized_template).to_s) rescue nil
end
protected
def store_template
begin
# puts "self.liquify_template_source = #{self.liquify_template_source.inspect}"
@template = ::Liquid::Template.parse(self.liquify_template_source, { :site => self.site })
@template.root.context.clear
# puts "@template = #{@template.inspect}"
# @template = Locomotive::Liquid::Template.parse(self)
if self.respond_to?(:after_parse_template) # kind of callback
self.send(:after_parse_template)
end
self.serialized_template = BSON::Binary.new(Marshal.dump(@template))
rescue ::Liquid::SyntaxError => error
self.errors.add :template, :liquid_syntax_error
end
end
end
end
end
end

View File

@ -4,10 +4,21 @@ module Locomotive
class Snippet < ::Liquid::Include class Snippet < ::Liquid::Include
def render(context) attr_accessor :partial
site = context.registers[:site]
partial = ::Liquid::Template.file_system.read_template_file(site, context[@template_name]) def initialize(tag_name, markup, tokens, context)
super
snippet = context[:site].snippets.where(:slug => @template_name.gsub('\'', '')).first
if snippet
@partial = ::Liquid::Template.parse(snippet.template, context)
@partial.root.context.clear
end
end
def render(context)
return '' if @partial.nil?
variable = context[@variable_name || @template_name[1..-2]] variable = context[@variable_name || @template_name[1..-2]]
@ -19,11 +30,11 @@ module Locomotive
output = (if variable.is_a?(Array) output = (if variable.is_a?(Array)
variable.collect do |variable| variable.collect do |variable|
context[@template_name[1..-2]] = variable context[@template_name[1..-2]] = variable
partial.render(context) @partial.render(context)
end end
else else
context[@template_name[1..-2]] = variable context[@template_name[1..-2]] = variable
partial.render(context) @partial.render(context)
end) end)
output output

View File

@ -10,7 +10,6 @@ module Locomotive
include ::Mongoid::Document include ::Mongoid::Document
include ::Mongoid::Timestamps include ::Mongoid::Timestamps
include ::Mongoid::CustomFields include ::Mongoid::CustomFields
include Locomotive::Liquid::LiquifyTemplate
end end
end end

View File

@ -11,7 +11,7 @@ puts "Starting test..."
site = Site.create :name => 'Benchmark Website', :subdomain => 'benchmark' site = Site.create :name => 'Benchmark Website', :subdomain => 'benchmark'
simple = site.pages.create :title => 'Simple', :slug => 'simple', :layout_template => %{ simple = site.pages.create :title => 'Simple', :slug => 'simple', :raw_template => %{
<html> <html>
<head></head> <head></head>
<body> <body>
@ -29,7 +29,7 @@ simple = site.pages.create :title => 'Simple', :slug => 'simple', :layout_templa
</html> </html>
} }
base = site.pages.create :title => 'Base page', :slug => 'base', :layout_template => %{ base = site.pages.create :title => 'Base page', :slug => 'base', :raw_template => %{
<html> <html>
<head></head> <head></head>
<body> <body>
@ -47,13 +47,13 @@ base = site.pages.create :title => 'Base page', :slug => 'base', :layout_templat
</html> </html>
} }
page_1 = site.pages.create :title => 'Page 1', :slug => 'page_1', :layout_template => %{ page_1 = site.pages.create :title => 'Page 1', :slug => 'page_1', :raw_template => %{
{% extends base %} {% extends base %}
{% block sidebar %}A sidebar here{% endblock %} {% block sidebar %}A sidebar here{% endblock %}
{% block body %}<div class="wrapper">{% block main %}DEFAULT MAIN CONTENT{% endblock %}</div>{% endblock %} {% block body %}<div class="wrapper">{% block main %}DEFAULT MAIN CONTENT{% endblock %}</div>{% endblock %}
} }
page_2 = site.pages.create :title => 'Page 2', :slug => 'page_2', :layout_template => %{ page_2 = site.pages.create :title => 'Page 2', :slug => 'page_2', :raw_template => %{
{% extends page_1 %} {% extends page_1 %}
{% block sidebar %}{{ block.super }} / INDEX sidebar{% endblock %} {% block sidebar %}{{ block.super }} / INDEX sidebar{% endblock %}
{% block main %}Lorem ipsum{% endblock %} {% block main %}Lorem ipsum{% endblock %}

View File

@ -56,59 +56,11 @@ Factory.define :page do |p|
end end
# Factory.define "unpub"
## Liquid templates ##
Factory.define :liquid_template do |t|
t.name 'Simple one'
t.slug 'simple_one'
t.value %{simple liquid template}
t.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end
# ## Layouts ##
# Factory.define :layout do |l|
# l.name '1 main column + sidebar'
# l.value %{<html>
# <head>
# <title>My website</title>
# </head>
# <body>
# <div id="sidebar">
# \{% block sidebar %\}
# DEFAULT SIDEBAR CONTENT
# \{% endblock %\}
# </div>
# <div id="main">
# \{% block main %\}
# DEFAULT MAIN CONTENT
# \{% endblock %\}
# </div>
# </body>
# </html>}
# l.site { Site.where(:subdomain => "acme").first || Factory(:site) }
# end
#
# Factory.define :base_layout, :parent => :layout do |l|
# l.name '1 main column + sidebar'
# l.value %{<html>
# <head>
# <title>My website</title>
# </head>
# <body>
# <div id="sidebar">\{\{ content_for_left_sidebar \}\}</div>
# <div id="main">\{\{ content_for_layout | textile \}\}</div>
# </body>
# </html>}
# end
## Snippets ## ## Snippets ##
Factory.define :snippet do |s| Factory.define :snippet do |s|
s.name 'My website title' s.name 'My website title'
s.slug 'header' s.slug 'header'
s.value %{<title>Acme</title>} s.template %{<title>Acme</title>}
s.site { Site.where(:subdomain => "acme").first || Factory(:site) } s.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end

View File

@ -1,20 +0,0 @@
require 'spec_helper'
describe Locomotive::Liquid::Tags::Snippet do
before(:each) do
Site.any_instance.stubs(:create_default_pages!).returns(true)
site = Factory.build(:site)
snippet = Factory.build(:snippet, :site => site)
snippet.send(:store_template)
site.snippets.stubs(:where).returns([snippet])
@context = ::Liquid::Context.new({}, {}, { :site => site })
end
it 'should render it' do
template = ::Liquid::Template.parse("{% include 'header' %}")
text = template.render(@context)
text.should == "<title>Acme</title>"
end
end

View File

@ -1,19 +0,0 @@
require 'spec_helper'
describe LiquidTemplate do
it 'should have a valid factory' do
Factory.build(:liquid_template).should be_valid
end
# Validations ##
%w{site name value}.each do |field|
it "should validate presence of #{field}" do
template = Factory.build(:liquid_template, field.to_sym => nil)
template.should_not be_valid
template.errors[field.to_sym].should == ["can't be blank"]
end
end
end

View File

@ -6,4 +6,14 @@ describe Snippet do
Factory.build(:snippet).should be_valid Factory.build(:snippet).should be_valid
end end
# Validations ##
%w{site name template}.each do |field|
it "should validate presence of #{field}" do
template = Factory.build(:snippet, field.to_sym => nil)
template.should_not be_valid
template.errors[field.to_sym].should == ["can't be blank"]
end
end
end end