fix a bug with inner templatized pages + write more specs + fix a bug about the action liquid tag + user_agent is now available in liquid

did committed Mar 30, 2016
commit 3262cbad0de0cf319fd088f74235d58f5737e5f4
Showing 10 changed files with 134 additions and 295 deletions
locomotive/steam/adapters/filesystem/sanitizers/page.rb b/lib/locomotive/steam/adapters/filesystem/sanitizers/page.rb +30 -35
@@ @@ -9,9 +9,8 @@ module Locomotive::Steam
def setup(scope)
super.tap do
- @ids, @parent_ids = {}, {}
- @content_types = {}
- @localized = locales.inject({}) { |m, l| m[l] = {}; m }
+ @ids, @parent_ids, @templatized_ids = {}, {}, {}
+ @localized = locales.inject({}) { |m, l| m[l] = {}; m }
end
end
@@ @@ -22,27 +21,31 @@ module Locomotive::Steam
locales.each do |locale|
set_default_redirect_type(entity, locale)
- modify_if_templatized(entity, locale)
end
+
+ check_and_mark_as_templatized(entity)
end
def apply_to_dataset(dataset)
sorted_collection(dataset.records.values).each do |page|
locales.each do |locale|
+ set_parent_id(page)
+
+ modify_if_parent_templatized(page, locale)
+
# the following method needs to be called first
set_fullpath_for(page, locale)
- set_parent_id(page)
use_default_locale_template_path(page, locale)
end
-
- modify_if_nested_templatized(page)
end
end
# when this is called, the @ids hash has been populated completely
def set_parent_id(page)
- return if page.not_found?
+ page._fullpath ||= page.attributes.delete(:_fullpath)
+
+ return if page._fullpath == '404'
parent_key = parent_fullpath(page)
@@ @@ -69,17 +72,7 @@ module Locomotive::Steam
end
end
- def modify_if_nested_templatized(page)
- if content_type = fetch_content_type(parent_fullpath(page))
- # not a templatized page but it becomes one because
- # its parent is one of them
- _modify_if_templatized(page, content_type)
- end
- end
-
def set_fullpath_for(page, locale)
- page._fullpath ||= page.attributes.delete(:_fullpath)
-
slug = fullpath = page.slug[locale].try(:to_s)
return if slug.blank?
@@ @@ -98,7 +91,7 @@ module Locomotive::Steam
page.depth = page[:_fullpath].split('/').size
- if page.depth == 1 && system_pages?(page)
+ if system_pages?(page)
page.depth = 0
end
@@ @@ -106,6 +99,7 @@ module Locomotive::Steam
end
def system_pages?(page)
+ page.depth == 1 &&
%w(index 404).include?(page.slug.values.compact.first)
end
@@ @@ -114,19 +108,11 @@ module Locomotive::Steam
end
def parent_fullpath(page)
- return nil if page._fullpath == 'index'
+ return nil if page._fullpath == 'index' || page._fullpath == '404'
path = page._fullpath.split('/')[0..-2].join('/')
path.blank? ? 'index' : path
end
- def fetch_content_type(fullpath)
- @content_types[fullpath]
- end
-
- def set_content_type(fullpath, value)
- @content_types[fullpath] = value
- end
-
def fetch_localized_fullpath(fullpath, locale)
@localized[locale][fullpath]
end
@@ @@ -139,17 +125,26 @@ module Locomotive::Steam
@ids[entity[:_fullpath]] = entity._id
end
- def modify_if_templatized(page, locale)
+ def check_and_mark_as_templatized(page)
if content_type = page[:content_type]
- _modify_if_templatized(page, content_type)
- page[:slug][locale] = Locomotive::Steam::WILDCARD
- set_content_type(page[:_fullpath], content_type)
+ mark_as_templatized(page, content_type)
end
end
- def _modify_if_templatized(page, content_type)
- page[:templatized] = true
- page[:target_klass_name] = "Locomotive::ContentEntry#{content_type}"
+ def mark_as_templatized(page, content_type)
+ @templatized_ids[page._id] = content_type
+ page[:templatized] = true
+ page[:target_klass_name] = "Locomotive::ContentEntry#{content_type}"
+ end
+
+ def modify_if_parent_templatized(page, locale)
+ parent_templatized = @templatized_ids[page.parent_id]
+
+ if page[:templatized]
+ page[:slug][locale] = Locomotive::Steam::WILDCARD unless parent_templatized
+ elsif parent_templatized
+ mark_as_templatized(page, parent_templatized)
+ end
end
end
locomotive/steam/initializers/sprockets.rb b/lib/locomotive/steam/initializers/sprockets.rb +8 -5
@@ @@ -6,15 +6,12 @@ require 'autoprefixer-rails'
require 'execjs'
- # Force ExecJS to select the best engine based on the current configuration.
- # It means that if, down the road, we load a different javascript engine,
- # the ExecJS runtime won't be affected.
- ExecJS.runtime
-
module Locomotive::Steam
class SprocketsEnvironment < ::Sprockets::Environment
+ attr_reader :steam_path
+
def initialize(root, options = {})
super(root)
@@ @@ -25,6 +22,12 @@ module Locomotive::Steam
install_yui_compressor(options)
install_autoprefixer
+
+ context_class.class_eval do
+ def asset_path(path, options = {})
+ path
+ end
+ end
end
private
locomotive/steam/liquid/tags/action.rb b/lib/locomotive/steam/liquid/tags/action.rb +54 -0
@@ @@ -0,0 +1,54 @@
+ module Locomotive
+ module Steam
+ module Liquid
+ module Tags
+
+ # Execute javascript code server side.
+ # The API allows you to:
+ # - access the current liquid context
+ # - modify the session
+ # - send emails
+ # - find / create / update content entries
+ #
+ # Usage:
+ #
+ # {% action "" %}
+ # {% for post in blog.posts %}
+ # {{ post.title }}
+ # {% endfor %}
+ # {% endconsume %}
+ #
+ class Action < ::Liquid::Block
+
+ Syntax = /(#{::Liquid::QuotedString}+)/o
+
+ def initialize(tag_name, markup, options)
+ if markup =~ Syntax
+ @description = $1.to_s
+ else
+ raise ::Liquid::SyntaxError.new("Syntax Error in 'action' - Valid syntax: action \"<description>\"")
+ end
+ super
+ end
+
+ def render(context)
+ Locomotive::Common::Logger.info "[action] executing #{@description}"
+ service(context).run(super, context['params'], context)
+ ''
+ end
+
+ private
+
+ def service(context)
+ context.registers[:services].action
+ end
+
+ end
+
+ ::Liquid::Template.register_tag('action'.freeze, Action)
+
+ end
+ end
+ end
+ end
+
locomotive/steam/middlewares/renderer.rb b/lib/locomotive/steam/middlewares/renderer.rb +1 -0
@@ @@ -103,6 +103,7 @@ module Locomotive::Steam
'ip_address' => request.ip,
'post?' => request.post?,
'base_url' => request.base_url,
+ 'user_agent' => request.user_agent,
'mounted_on' => mounted_on
}
end
locomotive/steam/services.rb b/lib/locomotive/steam/services.rb +1 -1
@@ @@ -63,7 +63,7 @@ module Locomotive
end
register :action do
- Steam::Action.new(current_site, email, content_entry)
+ Steam::ActionService.new(current_site, email, content_entry)
end
register :content_entry do
locomotive/steam/services/action_service.rb b/lib/locomotive/steam/services/action_service.rb +15 -21
@@ @@ -1,20 +1,10 @@
- # TODO: accessor to other services: email, content_entry CRUD actions
-
- # built-in functions (prefix by a namespace? or included in a module):
- # x site
- # x sendEmail(params) => nil
- # x setProp(key, value) # Liquid context (assigns)
- # x getProp(key) # Liquid context (assigns)
- # x setSessionProp(key, value)
- # x getSessionProp(key)
- # x allEntries(type, filters) => Array of hashes
- # x findEntry(type, <id_or_slug>)
- # - createEntry(<type>, attributes) => Hash (with id)
- # - updateEntry(<type>, <id_or_slug>, attributes)
-
- # liquid tag: {% action '<name>' %}<coffee script here>{% endaction %}
-
+ # Force ExecJS to select the best engine based on the current configuration.
+ # It means that if, down the road, we load a different javascript engine,
+ # the ExecJS runtime won't be affected.
require 'duktape'
+ require 'execjs'
+ ExecJS.runtimes.delete_if { |mod| mod.is_a?(ExecJS::DuktapeRuntime) }
+ ExecJS.instance_variable_set(:@runtime, ExecJS::Runtimes.autodetect)
module Locomotive
module Steam
@@ @@ -23,7 +13,7 @@ module Locomotive
BUILT_IN_FUNCTIONS = %w(
getProp
- sendProp
+ setProp
getSessionProp
setSessionProp
sendEmail
@@ @@ -39,12 +29,16 @@ module Locomotive
define_built_in_functions(context, liquid_context)
- context.exec_string <<-JS
+ script = <<-JS
function locomotiveAction(site, params) {
#{script}
}
JS
+ # puts script.inspect # DEBUG
+
+ context.exec_string script
+
context.call_prop('locomotiveAction', site.as_json, params)
end
@@ @@ -61,11 +55,11 @@ module Locomotive
end
def get_prop_lambda(liquid_context)
- -> (name) { liquid_context[name].as_json }
+ -> (name) { liquid_context[name].as_json.tap { |e| puts e.inspect } }
end
- def send_prop_lambda(liquid_context)
- -> (name, value) { liquid_context[name] = value }
+ def set_prop_lambda(liquid_context)
+ -> (name, value) { liquid_context.scopes.last[name] = value }
end
def get_session_prop_lambda(liquid_context)
spec/unit/liquid/tags/action_spec.rb +23 -0
@@ @@ -0,0 +1,23 @@
+ require 'spec_helper'
+
+ describe Locomotive::Steam::Liquid::Tags::Action do
+
+ let(:site) { instance_double('Site', default_locale: 'en') }
+ let(:source) { '{% action "random Javascript action" %}var foo = 42; setProp("foo", foo);{% endaction %}' }
+ let(:assigns) { {} }
+ let(:services) { Locomotive::Steam::Services.build_instance }
+ let(:context) { ::Liquid::Context.new(assigns, {}, { services: services }) }
+
+ before { allow(services).to receive(:current_site).and_return(site) }
+
+ subject { render_template(source, context) }
+
+ describe 'rendering' do
+
+ it { is_expected.to eq '' }
+
+ it { subject; expect(context['foo']).to eq 42.0 }
+
+ end
+
+ end
spec/unit/services/action_service_spec.rb +2 -2
@@ @@ -61,9 +61,9 @@ describe Locomotive::Steam::ActionService do
end
- describe 'sendProp' do
+ describe 'setProp' do
- let(:script) { "return sendProp('done', true);" }
+ let(:script) { "return setProp('done', true);" }
it { subject; expect(context['done']).to eq true }
spec/unit/services/content_entry_service_spec.rb +0 -82
@@ @@ -61,85 +61,3 @@ describe Locomotive::Steam::ContentEntryService do
end
end
-
- # describe '#submit' do
-
- # let(:slug) { nil }
- # let(:attributes) { { title: 'Hello world' } }
- # subject { service.submit(slug, attributes) }
-
- # it { is_expected.to eq nil }
-
- # context 'unknown content type' do
-
- # let(:slug) { 'articles' }
-
- # before { allow(type_repository).to receive(:by_slug).with('articles').and_return nil }
-
- # it { is_expected.to eq nil }
-
- # end
-
- # context 'existing content type' do
-
- # let(:unique_fields) { {} }
- # let(:first_validation) { false }
- # let(:errors) { [:title] }
- # let(:enabled) { true }
- # let(:type) { instance_double('Comments', public_submission_enabled: enabled) }
- # let(:entry) { instance_double('Entry', title: 'Hello world', content_type: type, valid?: first_validation, errors: errors, attributes: { title: 'Hello world' }, localized_attributes: []) }
- # let(:slug) { 'comments' }
-
- # before do
- # allow(type_repository).to receive(:by_slug).and_return(type)
- # allow(type_repository).to receive(:look_for_unique_fields).and_return(unique_fields)
- # allow(entry_repository).to receive(:build).with(attributes).and_return(entry)
- # end
-
- # context 'public submission disabled' do
-
- # let(:enabled) { false }
- # it { is_expected.to eq nil }
-
- # end
-
- # context 'valid' do
-
- # before { expect(entry_repository).to receive(:create) }
-
- # let(:first_validation) { true }
- # let(:errors) { {} }
-
- # it { is_expected.to eq entry }
- # it { expect(subject.errors.empty?).to eq true }
-
- # end
-
- # context 'not valid' do
-
- # before { expect(entry_repository).not_to receive(:create) }
-
- # it { is_expected.to eq entry }
- # it { expect(subject.errors).to eq([:title]) }
-
- # context 'with unique fields' do
-
- # let(:unique_fields) { { title: instance_double('Field', name: 'title') } }
-
- # before do
- # allow(entry_repository).to receive(:exists?).with(title: 'Hello world').and_return(true)
- # expect(entry.errors).to receive(:add).with(:title, :unique).and_return(true)
- # end
-
- # it { is_expected.to eq entry }
- # it { expect(subject.errors).to eq([:title]) }
-
- # end
-
- # end
-
- # end
-
- # end
-
- # end
spec/unit/services/entry_submission_service_spec.rb +0 -149
@@ @@ -66,152 +66,3 @@ describe Locomotive::Steam::EntrySubmissionService do
end
end
-
- # let(:site) { instance_double('Site', default_locale: 'en') }
- # let(:locale) { 'en' }
- # let(:type_repository) { instance_double('ContentTypeRepository') }
- # let(:entry_repository) { instance_double('Repository', site: site, locale: locale, content_type_repository: type_repository) }
- # let(:service) { described_class.new(type_repository, entry_repository, locale) }
-
- # before { allow(entry_repository).to receive(:with).and_return(entry_repository) }
-
- # describe '#find' do
-
- # let(:type_slug) { 'articles' }
- # let(:slug) { 'hello-world' }
- # subject { service.find(type_slug, slug) }
-
- # context 'unknown content type' do
-
- # before { allow(type_repository).to receive(:by_slug).and_return(nil) }
- # it { is_expected.to eq nil }
-
- # end
-
- # context 'existing content type' do
-
- # let(:type) { instance_double('Articles') }
- # let(:entry) { instance_double('Entry', title: 'Hello world', content_type: type, attributes: { title: 'Hello world' }, localized_attributes: []) }
-
- # before do
- # allow(type_repository).to receive(:by_slug).and_return(type)
- # allow(entry_repository).to receive(:by_slug).with('hello-world').and_return(entry)
- # end
-
- # it { is_expected.to eq entry }
-
- # end
-
- # end
-
- # describe '#to_json' do
-
- # let(:entry) { nil }
- # subject { service.to_json(entry) }
-
- # it { is_expected.to eq nil }
-
- # context 'existing content entry' do
-
- # let(:field) { instance_double('TitleField', name: :title, type: :string) }
- # let(:fields) { [field] }
- # let(:type) { instance_double('Articles', slug: 'articles', label_field_name: :title, fields_by_name: { title: field }, persisted_field_names: [:title]) }
- # let(:entry) { Locomotive::Steam::ContentEntry.new(_slug: 'hello-world', title: 'Hello world', content_type: type) }
-
- # before { allow(type).to receive(:fields).and_return(instance_double('FieldRepository', all: fields)) }
-
- # it { is_expected.to match %r{{"_id":null,"_slug":"hello-world","_label":"Hello world","_visible":true,"_position":0,"content_type_slug":"articles","created_at":"[^\"]+","updated_at":"[^\"]+","title":"Hello world"}} }
-
- # context 'with errors' do
-
- # before { entry.errors.add(:title, "can't be blank") }
-
- # it { is_expected.to match %r{,\"errors\":\{\"title\":\[\"can't be blank\"\]\}} }
-
- # end
-
- # end
-
- # end
-
- # describe '#submit' do
-
- # let(:slug) { nil }
- # let(:attributes) { { title: 'Hello world' } }
- # subject { service.submit(slug, attributes) }
-
- # it { is_expected.to eq nil }
-
- # context 'unknown content type' do
-
- # let(:slug) { 'articles' }
-
- # before { allow(type_repository).to receive(:by_slug).with('articles').and_return nil }
-
- # it { is_expected.to eq nil }
-
- # end
-
- # context 'existing content type' do
-
- # let(:unique_fields) { {} }
- # let(:first_validation) { false }
- # let(:errors) { [:title] }
- # let(:enabled) { true }
- # let(:type) { instance_double('Comments', public_submission_enabled: enabled) }
- # let(:entry) { instance_double('Entry', title: 'Hello world', content_type: type, valid?: first_validation, errors: errors, attributes: { title: 'Hello world' }, localized_attributes: []) }
- # let(:slug) { 'comments' }
-
- # before do
- # allow(type_repository).to receive(:by_slug).and_return(type)
- # allow(type_repository).to receive(:look_for_unique_fields).and_return(unique_fields)
- # allow(entry_repository).to receive(:build).with(attributes).and_return(entry)
- # end
-
- # context 'public submission disabled' do
-
- # let(:enabled) { false }
- # it { is_expected.to eq nil }
-
- # end
-
- # context 'valid' do
-
- # before { expect(entry_repository).to receive(:create) }
-
- # let(:first_validation) { true }
- # let(:errors) { {} }
-
- # it { is_expected.to eq entry }
- # it { expect(subject.errors.empty?).to eq true }
-
- # end
-
- # context 'not valid' do
-
- # before { expect(entry_repository).not_to receive(:create) }
-
- # it { is_expected.to eq entry }
- # it { expect(subject.errors).to eq([:title]) }
-
- # context 'with unique fields' do
-
- # let(:unique_fields) { { title: instance_double('Field', name: 'title') } }
-
- # before do
- # allow(entry_repository).to receive(:exists?).with(title: 'Hello world').and_return(true)
- # expect(entry.errors).to receive(:add).with(:title, :unique).and_return(true)
- # end
-
- # it { is_expected.to eq entry }
- # it { expect(subject.errors).to eq([:title]) }
-
- # end
-
- # end
-
- # end
-
- # end
-
- # end