write specs + refactor the page liquid drop

did committed Feb 06, 2015
commit 7888dc27ab3bf62f0ce1e05a4f39b010b4f8eb7f
Showing 11 changed files with 379 additions and 337 deletions
locomotive/steam/liquid.rb b/lib/locomotive/steam/liquid.rb +1 -2
@@ @@ -1,10 +1,9 @@
require 'solid'
- # require 'locomotive/models'
- # require_relative 'liquid/scopeable'
require_relative 'liquid/errors'
require_relative 'liquid/patches'
require_relative 'liquid/drops/base'
+ require_relative 'liquid/drops/proxy_collection'
require_relative 'liquid/tags/hybrid'
require_relative 'liquid/tags/path_helper'
locomotive/steam/liquid/drops/content_types.rb b/lib/locomotive/steam/liquid/drops/content_types.rb +121 -119
@@ @@ -1,119 +1,121 @@
- # module Locomotive
- # module Liquid
- # module Drops
- # class ContentTypes < ::Liquid::Drop
-
- # def before_method(meth)
- # type = @context.registers[:site].content_types.where(slug: meth.to_s).first
- # ContentTypeProxyCollection.new(type)
- # end
-
- # end
-
- # class ContentTypeProxyCollection < ProxyCollection
-
- # def initialize(content_type)
- # @content_type = content_type
- # @collection = nil
- # end
-
- # def public_submission_url
- # site = @context.registers[:controller].send(:current_site)
- # @context.registers[:controller].locomotive_entry_submissions_path(site, @content_type.slug)
- # end
-
- # def api
- # Locomotive.log :warn, "[Liquid template] the api for content_types has been deprecated and replaced by public_submission_url instead."
- # { 'create' => public_submission_url }
- # end
-
- # def before_method(meth)
- # klass = @content_type.entries.klass # delegate to the proxy class
-
- # if (meth.to_s =~ /^group_by_(.+)$/) == 0
- # klass.send(:group_by_select_option, $1, @content_type.order_by_definition)
- # elsif (meth.to_s =~ /^(.+)_options$/) == 0
- # klass.send(:"#{$1}_options").map { |option| option['name'] }
- # else
- # Locomotive.log :warn, "[Liquid template] trying to call #{meth} on a content_type object"
- # end
- # end
-
- # protected
-
- # def collection
- # options = {}
-
- # if @context['with_scope']
- # self.modify_with_scope
-
- # options = { where: @context['with_scope'] }
-
- # options[:order_by] = options[:where].delete(:order_by)
- # end
-
- # @collection ||= @content_type.ordered_entries(options).visible
- # end
-
- # # Modify the attributes of the with_scope tag so that
- # # they can be resolved by MongoDB.
- # #
- # def modify_with_scope
- # @context['with_scope'].dup.each do |key, value|
- # field = @content_type.find_entries_custom_field(key.to_s)
-
- # next if field.nil?
-
- # case field.type.to_sym
- # when :belongs_to
- # self.modify_with_scope_key(key, "#{key.to_s}_id", self.object_to_id(field, value))
- # when :many_to_many
- # self.modify_with_scope_key(key, "#{key.to_s.singularize}_ids", self.object_to_id(field, value))
- # when :select
- # option = field.select_options.detect { |option| [option.name, option._id.to_s].include?(value) }
- # self.modify_with_scope_key(key, "#{key.to_s}_id", option.try(:_id))
- # end
- # end
- # end
-
- # # Change the value of a key of the with_scope depending of its type.
- # # If the key is a Origin::Key, we only change the name.
- # # If the key is a String, we replace it.
- # #
- # # @param [ Object ] key Either a String or a Origin::Key
- # # @param [ String ] name The new name of the key
- # # @param [ String ] value The new value associated to the key
- # #
- # def modify_with_scope_key(key, name, value)
- # if key.respond_to?(:operator)
- # key.instance_variable_set :@name, name
- # @context['with_scope'][key] = value
- # else
- # @context['with_scope'].delete(key)
- # @context['with_scope'][name] = value
- # end
- # end
-
- # # Get the _id attribute of a object or a list of objects which
- # # can include String (needed to retrieve a model
- # # based on its permalink or its label field) or ContentEntry instances.
- # #
- # # @param [ Object ] field The custom field
- # # @param [ Object ] value An object (content entry or label) or a list of objects
- # #
- # def object_to_id(field, value)
- # if value.respond_to?(:map)
- # value.map { |el| self.object_to_id(field, el) }
- # elsif value.respond_to?(:_id)
- # value._id
- # else
- # model = Locomotive::ContentType.class_name_to_content_type(field.class_name, @content_type.site)
- # model.entries.or({ _slug: value }, { model.label_field_name => value }).first.try(:_id)
- # end
- # end
-
- # end
-
- # end
- # end
- # end
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
+ class ContentTypes < ::Liquid::Drop
+
+ def before_method(meth)
+ repository = @context.registers[:services].repositories.content_type
+ ContentTypeProxyCollection.new(repository.by_slug(meth.to_s))
+ end
+
+ end
+
+ class ContentTypeProxyCollection < ProxyCollection
+
+ def initialize(content_type)
+ @content_type = content_type
+ @collection = nil
+ end
+
+ def public_submission_url
+ site = @context.registers[:controller].send(:current_site)
+ @context.registers[:controller].locomotive_entry_submissions_path(site, @content_type.slug)
+ end
+
+ def api
+ Locomotive.log :warn, "[Liquid template] the api for content_types has been deprecated and replaced by public_submission_url instead."
+ { 'create' => public_submission_url }
+ end
+
+ def before_method(meth)
+ klass = @content_type.entries.klass # delegate to the proxy class
+
+ if (meth.to_s =~ /^group_by_(.+)$/) == 0
+ klass.send(:group_by_select_option, $1, @content_type.order_by_definition)
+ elsif (meth.to_s =~ /^(.+)_options$/) == 0
+ klass.send(:"#{$1}_options").map { |option| option['name'] }
+ else
+ Locomotive.log :warn, "[Liquid template] trying to call #{meth} on a content_type object"
+ end
+ end
+
+ protected
+
+ def collection
+ options = {}
+
+ if @context['with_scope']
+ self.modify_with_scope
+
+ options = { where: @context['with_scope'] }
+
+ options[:order_by] = options[:where].delete(:order_by)
+ end
+
+ @collection ||= @content_type.ordered_entries(options).visible
+ end
+
+ # Modify the attributes of the with_scope tag so that
+ # they can be resolved by MongoDB.
+ #
+ def modify_with_scope
+ @context['with_scope'].dup.each do |key, value|
+ field = @content_type.find_entries_custom_field(key.to_s)
+
+ next if field.nil?
+
+ case field.type.to_sym
+ when :belongs_to
+ self.modify_with_scope_key(key, "#{key.to_s}_id", self.object_to_id(field, value))
+ when :many_to_many
+ self.modify_with_scope_key(key, "#{key.to_s.singularize}_ids", self.object_to_id(field, value))
+ when :select
+ option = field.select_options.detect { |option| [option.name, option._id.to_s].include?(value) }
+ self.modify_with_scope_key(key, "#{key.to_s}_id", option.try(:_id))
+ end
+ end
+ end
+
+ # Change the value of a key of the with_scope depending of its type.
+ # If the key is a Origin::Key, we only change the name.
+ # If the key is a String, we replace it.
+ #
+ # @param [ Object ] key Either a String or a Origin::Key
+ # @param [ String ] name The new name of the key
+ # @param [ String ] value The new value associated to the key
+ #
+ def modify_with_scope_key(key, name, value)
+ if key.respond_to?(:operator)
+ key.instance_variable_set :@name, name
+ @context['with_scope'][key] = value
+ else
+ @context['with_scope'].delete(key)
+ @context['with_scope'][name] = value
+ end
+ end
+
+ # Get the _id attribute of a object or a list of objects which
+ # can include String (needed to retrieve a model
+ # based on its permalink or its label field) or ContentEntry instances.
+ #
+ # @param [ Object ] field The custom field
+ # @param [ Object ] value An object (content entry or label) or a list of objects
+ #
+ def object_to_id(field, value)
+ if value.respond_to?(:map)
+ value.map { |el| self.object_to_id(field, el) }
+ elsif value.respond_to?(:_id)
+ value._id
+ else
+ model = Locomotive::ContentType.class_name_to_content_type(field.class_name, @content_type.site)
+ model.entries.or({ _slug: value }, { model.label_field_name => value }).first.try(:_id)
+ end
+ end
+
+ end
+
+ end
+ end
+ end
+ end
locomotive/steam/liquid/drops/page.rb b/lib/locomotive/steam/liquid/drops/page.rb +95 -115
@@ @@ -1,115 +1,95 @@
- # module Locomotive
- # module Liquid
- # module Drops
- # class Page < Base
-
- # delegate :seo_title, :meta_keywords, :meta_description, :redirect_url, :handle, to: :@_source
-
- # def title
- # title = @_source.templatized? ? @context['entry'].try(:_label) : nil
- # title || @_source.title
- # end
-
- # def slug
- # slug = @_source.templatized? ? @context['entry'].try(:_slug).try(:singularize) : nil
- # slug || @_source.slug
- # end
-
- # def original_title
- # @_source.title
- # end
-
- # def original_slug
- # @_source.slug
- # end
-
- # def parent
- # @parent ||= @_source.parent.to_liquid
- # end
-
- # def breadcrumbs
- # @breadcrumbs ||= liquify(*@_source.ancestors_and_self)
- # end
-
- # def children
- # @children ||= liquify(*@_source.children)
- # end
-
- # def fullpath
- # @fullpath ||= @_source.fullpath
- # end
-
- # def depth
- # @_source.depth
- # end
-
- # def listed?
- # @_source.listed?
- # end
-
- # def published?
- # @_source.published?
- # end
-
- # def redirect?
- # @_source.redirect?
- # end
-
- # def is_layout?
- # @_source.is_layout?
- # end
-
- # def templatized?
- # @_source.templatized?
- # end
-
- # def content_type
- # if @_source.content_type
- # ContentTypeProxyCollection.new(@_source.content_type)
- # else
- # nil
- # end
- # end
-
- # def editable_elements
- # @editable_elements_hash ||= build_editable_elements_hash
- # end
-
- # def before_method(meth)
- # # @deprecated
- # @_source.editable_elements.where(slug: meth).try(:first).try(:content)
- # end
-
- # private
-
- # def build_editable_elements_hash
- # {}.tap do |hash|
- # @_source.editable_elements.each do |el|
- # safe_slug = el.slug.parameterize.underscore
- # keys = el.block.try(:split, '/').try(:compact) || []
-
- # _hash = _build_editable_elements_hashes(hash, keys)
-
- # _hash[safe_slug] = el.content
- # end
- # end
- # end
-
- # def _build_editable_elements_hashes(hash, keys)
- # _hash = hash
-
- # keys.each do |key|
- # safe_key = key.parameterize.underscore
-
- # _hash[safe_key] = {} if _hash[safe_key].nil?
-
- # _hash = _hash[safe_key]
- # end
-
- # _hash
- # end
-
- # end
- # end
- # end
- # end
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
+ class Page < Base
+
+ delegate :fullpath, :depth, :seo_title, :meta_keywords, :meta_description, :redirect_url, :handle, to: :@_source
+ delegate :listed?, :published?, :redirect?, :is_layout?, :templatized?, to: :@_source
+
+ def title
+ title = @_source.templatized? ? @context['entry'].try(:_label) : nil
+ title || @_source.title
+ end
+
+ def slug
+ slug = @_source.templatized? ? @context['entry'].try(:_slug).try(:singularize) : nil
+ slug || @_source.slug
+ end
+
+ def original_title
+ @_source.title
+ end
+
+ def original_slug
+ @_source.slug
+ end
+
+ def parent
+ @parent ||= repository.parent_of(@_source).to_liquid
+ end
+
+ def breadcrumbs
+ @breadcrumbs ||= liquify(*repository.ancestors_of(@_source))
+ end
+
+ def children
+ @children ||= liquify(*repository.children_of(@_source))
+ end
+
+ def content_type
+ if @_source.content_type
+ # content_type can be either the slug of a content type or a content type
+ content_type = content_type_repository.by_slug(@_source.content_type)
+ ContentTypeProxyCollection.new(content_type)
+ else
+ nil
+ end
+ end
+
+ def editable_elements
+ @editable_elements_hash ||= build_editable_elements_hash
+ end
+
+ private
+
+ def repository
+ @context.registers[:services].repositories.page
+ end
+
+ def content_type_repository
+ @context.registers[:services].repositories.content_type
+ end
+
+ def build_editable_elements_hash
+ {}.tap do |hash|
+ repository.editable_elements_of(@_source).each do |el|
+ safe_slug = el.slug.parameterize.underscore
+ keys = el.block.try(:split, '/').try(:compact) || []
+
+ _hash = _build_editable_elements_hashes(hash, keys)
+
+ _hash[safe_slug] = el.content
+ end
+ end
+ end
+
+ def _build_editable_elements_hashes(hash, keys)
+ _hash = hash
+
+ keys.each do |key|
+ safe_key = key.parameterize.underscore
+
+ _hash[safe_key] = {} if _hash[safe_key].nil?
+
+ _hash = _hash[safe_key]
+ end
+
+ _hash
+ end
+
+ end
+ end
+ end
+ end
+ end
locomotive/steam/liquid/drops/proxy_collection.rb b/lib/locomotive/steam/liquid/drops/proxy_collection.rb +22 -56
@@ @@ -1,64 +1,30 @@
- # module Locomotive
- # module Liquid
- # module Drops
+ module Locomotive
+ module Steam
+ module Liquid
+ module Drops
- # class ProxyCollection < ::Liquid::Drop
+ class ProxyCollection < ::Liquid::Drop
- # def initialize(collection)
- # @collection = collection
- # end
+ delegate :first, :last, :each, :each_with_index, :empty?, :any?, to: :@collection
- # def first
- # self.collection.first
- # end
+ def initialize(collection)
+ @collection = collection
+ end
- # def last
- # self.collection.last
- # end
+ def count
+ @count ||= @collection.count
+ end
- # def each(&block)
- # self.collection.each(&block)
- # end
+ def all
+ @collection
+ end
- # def each_with_index(&block)
- # self.collection.each_with_index(&block)
- # end
+ alias :size :count
+ alias :length :count
- # def count
- # @count ||= self.collection.count
- # end
+ end
- # def all
- # self.collection
- # end
-
- # alias :size :count
- # alias :length :count
-
- # def empty
- # self.collection.empty?
- # end
-
- # def any
- # self.collection.any?
- # end
-
- # def content_type
-
- # end
-
- # protected
-
- # def paginate(options = {})
- # @collection = collection.page(options[:page]).per(options[:per_page])
- # end
-
- # def collection
- # @collection
- # end
-
- # end
-
- # end
- # end
- # end
+ end
+ end
+ end
+ end
locomotive/steam/repositories.rb b/lib/locomotive/steam/repositories.rb +4 -0
@@ @@ -22,6 +22,10 @@ module Locomotive
Repositories::Page.new(current_site)
end
+ register :content_type do
+ Repositories::ContentType.new(current_site)
+ end
+
register :snippet do
Repositories::Snippet.new(current_site)
end
locomotive/steam/repositories/content_type.rb b/lib/locomotive/steam/repositories/content_type.rb +19 -0
@@ @@ -0,0 +1,19 @@
+ module Locomotive
+ module Steam
+ module Repositories
+
+ class ContentType < Struct.new(:site)
+
+ def by_slug(slug_or_content_type)
+ if slug_or_content_type.is_a?(String)
+ site.where(slug: slug_or_content_type).first
+ else
+ slug_or_content_type
+ end
+ end
+
+ end
+
+ end
+ end
+ end
locomotive/steam/repositories/content_types_repository.rb b/lib/locomotive/steam/repositories/content_types_repository.rb +0 -14
@@ @@ -1,14 +0,0 @@
- # module Locomotive
- # module Steam
- # module Repositories
- # class ContentTypesRepository
- # include Repository
- # def [](slug)
- # query(:en) do
- # where('slug.eq' => slug.to_s)
- # end.first
- # end
- # end
- # end
- # end
- # end
locomotive/steam/repositories/page.rb b/lib/locomotive/steam/repositories/page.rb +8 -0
@@ @@ -24,10 +24,18 @@ module Locomotive
page.parent
end
+ def ancestors_of(page)
+ page.ancestors_and_self
+ end
+
def children_of(page)
page.children
end
+ def editable_elements_of(page)
+ page.editable_elements
+ end
+
def editable_element_for(page, block, slug)
page.editable_elements.where(block: block, slug: slug).first
end
locomotive/steam/repositories/pages_repository.rb b/lib/locomotive/steam/repositories/pages_repository.rb +0 -23
@@ @@ -1,23 +0,0 @@
- # module Locomotive
- # module Steam
- # module Repositories
- # class PagesRepository
- # include Repository
- # attr_accessor :current_locale
-
- # def [](path)
- # query(current_locale) do
- # where('fullpath.eq' => path)
- # end.first
- # end
-
- # def matching_paths(paths)
- # query(current_locale) do
- # where('fullpath.in' => paths)
- # order_by('position ASC')
- # end
- # end
- # end
- # end
- # end
- # end
spec/unit/liquid/drops/page_spec.rb +103 -0
@@ @@ -0,0 +1,103 @@
+ require 'spec_helper'
+
+ describe Locomotive::Steam::Liquid::Drops::Page do
+
+ let(:assigns) { {} }
+ let(:services) { Locomotive::Steam::Services.build_instance }
+ let(:context) { ::Liquid::Context.new(assigns, {}, { services: services }) }
+ let(:page) { instance_double('Page', id: 42, title: 'Index', slug: 'index', fullpath: 'index', content_type: nil, depth: 1, templatized?: false, listed?: true, published?: true, is_layout?: true, redirect?: false, seo_title: 'seo title', redirect_url: '/', handle: 'index', meta_keywords: 'keywords', meta_description: 'description') }
+ let(:drop) { Locomotive::Steam::Liquid::Drops::Page.new(page).tap { |d| d.context = context } }
+
+ subject { drop }
+
+ it 'gives access to general attributes' do
+ expect(subject.id).to eq 42
+ expect(subject.title).to eq 'Index'
+ expect(subject.original_title).to eq 'Index'
+ expect(subject.slug).to eq 'index'
+ expect(subject.original_slug).to eq 'index'
+ expect(subject.fullpath).to eq 'index'
+ expect(subject.content_type).to eq nil
+ expect(subject.depth).to eq 1
+ expect(subject.redirect_url).to eq '/'
+ expect(subject.handle).to eq 'index'
+ expect(subject.seo_title).to eq 'seo title'
+ expect(subject.meta_keywords).to eq 'keywords'
+ expect(subject.meta_description).to eq 'description'
+ expect(subject.listed?).to eq true
+ expect(subject.redirect?).to eq false
+ expect(subject.is_layout?).to eq true
+ expect(subject.published?).to eq true
+ expect(subject.templatized?).to eq false
+ end
+
+ describe '#parent' do
+
+ let(:parent) { instance_double('ParentPage', to_liquid: { 'title' => 'Parent' }) }
+
+ before do
+ allow(services.repositories.page).to receive(:parent_of).with(page).and_return(parent)
+ end
+
+ it { expect(subject.parent).to eq({ 'title' => 'Parent' }) }
+
+ end
+
+ describe '#breadcrumbs' do
+
+ let(:ancestors) { [instance_double('ParentPage', to_liquid: { 'title' => 'Parent' })] }
+
+ before do
+ allow(services.repositories.page).to receive(:ancestors_of).with(page).and_return(ancestors)
+ end
+
+ it { expect(subject.breadcrumbs).to eq([{ 'title' => 'Parent' }]) }
+
+ end
+
+ describe '#children' do
+
+ let(:children) { [instance_double('ChildPage', to_liquid: { 'title' => 'Child' })] }
+
+ before do
+ allow(services.repositories.page).to receive(:children_of).with(page).and_return(children)
+ end
+
+ it { expect(subject.children).to eq([{ 'title' => 'Child' }]) }
+
+ end
+
+ describe '#editable_elements' do
+
+ let(:elements) { [instance_double('EditableElement', block: 'top/left', slug: 'banner', content: 'Hello world')] }
+
+ before do
+ allow(services.repositories.page).to receive(:editable_elements_of).with(page).and_return(elements)
+ end
+
+ it { expect(subject.editable_elements).to eq({ 'top' => { 'left' => { 'banner' => 'Hello world' } } }) }
+
+ end
+
+ context 'templatized page' do
+
+ let(:entry) { liquid_instance_double('ContentEntry', _label: 'First Article', _slug: 'first-article') }
+ let(:assigns) { { 'entry' => entry } }
+ let(:page) { instance_double('Page', id: 42, title: 'Index', slug: 'index', templatized?: true, content_type: 'articles') }
+
+ it { expect(subject.title).to eq 'First Article' }
+ it { expect(subject.slug).to eq 'first-article' }
+
+ describe '#content_type' do
+
+ before do
+ allow(services.repositories.content_type).to receive(:by_slug).with('articles').and_return('Articles')
+ end
+
+ it { expect(subject.content_type).not_to eq nil }
+
+ end
+
+ end
+
+ end
spec/unit/liquid/drops/site_spec.rb +6 -8
@@ @@ -9,14 +9,12 @@ describe Locomotive::Steam::Liquid::Drops::Site do
subject { drop }
- describe 'general attributes' do
-
- it { expect(subject.name).to eq 'Locomotive' }
- it { expect(subject.seo_title).to eq 'seo title' }
- it { expect(subject.meta_keywords).to eq 'keywords' }
- it { expect(subject.meta_description).to eq 'description' }
- it { expect(subject.domains).to eq ['acme.org'] }
-
+ it 'gives access to general attributes' do
+ expect(subject.name).to eq 'Locomotive'
+ expect(subject.seo_title).to eq 'seo title'
+ expect(subject.meta_keywords).to eq 'keywords'
+ expect(subject.meta_description).to eq 'description'
+ expect(subject.domains).to eq ['acme.org']
end
describe '#index' do