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 |