handle the associations of a content entry + write more specs
did
committed Feb 14, 2015
commit 05ae9622e46c0f4f0d030b296007a0fbaea5c8df
Showing 10
changed files with
192 additions
and 43 deletions
locomotive/steam/repositories/filesystem.rb b/lib/locomotive/steam/repositories/filesystem.rb
+1
-1
| @@ | @@ -41,7 +41,7 @@ module Locomotive |
| register :content_entry do | |
| Filesystem::ContentEntry.new( | |
| YAMLLoaders::ContentEntry.new(options[:path], cache), | |
| - | current_site, current_locale) |
| + | current_site, current_locale, content_type) |
| end | |
| register :theme_asset do | |
locomotive/steam/repositories/filesystem/content_entry.rb b/lib/locomotive/steam/repositories/filesystem/content_entry.rb
+57
-15
| @@ | @@ -3,7 +3,7 @@ module Locomotive |
| module Repositories | |
| module Filesystem | |
| - | class ContentEntry < Struct.new(:loader, :site, :current_locale) |
| + | class ContentEntry < Struct.new(:loader, :site, :current_locale, :content_type_repository) |
| include Concerns::Queryable | |
| @@ | @@ -11,15 +11,23 @@ module Locomotive |
| # Engine: ??? | |
| def all(type, conditions = {}) | |
| - | conditions ||= {} |
| + | conditions = { _visible: true }.merge(conditions || {}) |
| - | # TODO: order_by goes here (get settings from the type) |
| + | # priority: |
| + | # 1/ order_by passed in the conditions parameter |
| + | # 2/ the default order (_position) defined in the content type |
| + | order_by = conditions.delete(:order_by) || type.order_by |
| query(type) do | |
| - | where(conditions.merge(_visible: true)).order_by(conditions.delete(:order_by)) |
| + | where(conditions).order_by(order_by) |
| end.all | |
| end | |
| + | # Engine: not necessary |
| + | def by_slug(type, slug) |
| + | query(type) { where(_slug: slug) }.first |
| + | end |
| + | |
| # Engine: entry.name :-) | |
| def value_for(name, entry, conditions = {}) | |
| value = entry.send(name) | |
| @@ | @@ -31,17 +39,6 @@ module Locomotive |
| end | |
| end | |
| - | # Note: |
| - | def association(metadata, conditions = {}) |
| - | # only visible entries |
| - | # conditions[:_visible] = true |
| - | |
| - | # order_by = conditions.delete(:order_by).try(:split) |
| - | |
| - | # association.filtered(conditions, order_by) |
| - | raise 'TODO filter' |
| - | end |
| - | |
| # Engine: entry.next | |
| def next(entry) | |
| raise 'TODO next' | |
| @@ | @@ -64,6 +61,51 @@ module Locomotive |
| private | |
| + | def type_from(slug) |
| + | content_type_repository.by_slug(slug) |
| + | end |
| + | |
| + | def localized_slug(entry) |
| + | if (values = entry._slug).is_a?(Hash) |
| + | values[current_locale] |
| + | else |
| + | values |
| + | end |
| + | end |
| + | |
| + | def association(metadata, conditions = {}) |
| + | case metadata.type |
| + | when :belongs_to then belongs_to_association(metadata) |
| + | when :has_many then has_many_association(metadata, conditions) |
| + | when :many_to_many then many_to_many_association(metadata, conditions) |
| + | end |
| + | end |
| + | |
| + | def belongs_to_association(metadata) |
| + | type = type_from(metadata.target_class_slug) |
| + | by_slug(type, metadata.target_slugs.first) |
| + | end |
| + | |
| + | def has_many_association(metadata, conditions) |
| + | many_association(metadata, |
| + | { metadata.target_field => localized_slug(metadata.source) }.merge(conditions)) |
| + | end |
| + | |
| + | def many_to_many_association(metadata, conditions) |
| + | many_association(metadata, |
| + | { '_slug.in' => metadata.target_slugs }.merge(conditions)) |
| + | end |
| + | |
| + | def many_association(metadata, conditions) |
| + | type = type_from(metadata.target_class_slug) |
| + | |
| + | if order_by = metadata.order_by |
| + | conditions = { order_by: order_by }.merge(conditions) |
| + | end |
| + | |
| + | all(type, conditions) |
| + | end |
| + | |
| def memoized_collection(content_type) | |
| slug = content_type.slug | |
| @collections ||= {} | |
locomotive/steam/repositories/filesystem/models/content_entry.rb b/lib/locomotive/steam/repositories/filesystem/models/content_entry.rb
+7
-2
| @@ | @@ -14,8 +14,10 @@ module Locomotive |
| def initialize(attributes = {}) | |
| super({ | |
| - | _visible: true, |
| - | _position: 0 |
| + | _visible: true, |
| + | _position: 0, |
| + | created_at: Time.now, |
| + | updated_at: Time.now |
| }.merge(attributes)) | |
| end | |
| @@ | @@ -68,6 +70,9 @@ module Locomotive |
| class AssociationMetadata < Struct.new(:type, :source, :field, :target_slugs) | |
| def association; true; end | |
| + | def inverse_of; field.inverse_of; end |
| + | def target_class_slug; field.class_name; end |
| + | def order_by; field[:order_by]; end |
| end | |
| end | |
locomotive/steam/repositories/filesystem/models/content_type.rb b/lib/locomotive/steam/repositories/filesystem/models/content_type.rb
+12
-0
| @@ | @@ -8,6 +8,13 @@ module Locomotive |
| attr_accessor :fields, :fields_by_name | |
| + | def initialize(attributes = {}) |
| + | super({ |
| + | order_by: '_position', |
| + | order_direction: 'asc' |
| + | }.merge(attributes)) |
| + | end |
| + | |
| def label_field_name | |
| (self[:label_field_name] || fields.first.name).to_sym | |
| end | |
| @@ | @@ -16,6 +23,11 @@ module Locomotive |
| query_fields { where(localized: true) }.all.map(&:name) | |
| end | |
| + | def order_by |
| + | name = self[:order_by] == 'manually' ? '_position' : self[:order_by] |
| + | "#{name} #{self.order_direction}" |
| + | end |
| + | |
| def query_fields(&block) | |
| Filesystem::MemoryAdapter::Query.new(fields, &block) | |
| end | |
spec/support.rb
+1
-0
| @@ | @@ -4,4 +4,5 @@ require_relative 'support/liquid' |
| require_relative 'support/matchers/hash' | |
| require_relative 'support/examples/matching_locale' | |
| require_relative 'support/examples/locale_file' | |
| + | require_relative 'support/time' |
| require_relative 'support/pry' | |
spec/support/time.rb
+3
-0
| @@ | @@ -0,0 +1,3 @@ |
| + | require 'chronic' |
| + | Time.zone = 'UTC' |
| + | Chronic.time_class = Time.zone |
spec/unit/repositories/filesystem/content_entry_spec.rb
+61
-24
| @@ | @@ -3,12 +3,13 @@ require 'spec_helper' |
| describe Locomotive::Steam::Repositories::Filesystem::ContentEntry do | |
| # let(:fields) { [{ title: { hint: 'Title of the article' } }, { author: { type: 'string', label: 'Fullname of the author' } }] } | |
| - | let(:type) { instance_double('Articles', slug: 'articles', label_field_name: :title, localized_fields_names: [:title], fields_by_name: { title: instance_double('Field', type: :string) }) } |
| + | let(:type) { instance_double('Articles', slug: 'articles', order_by: nil, label_field_name: :title, localized_fields_names: [:title], fields_by_name: { title: instance_double('Field', type: :string) }) } |
| let(:loader) { instance_double('Loader', list_of_attributes: [{ content_type: type, _position: 0, _label: 'Update #1', title: { fr: 'Mise a jour #1' }, text: { en: 'added some free stuff', fr: 'phrase FR' }, date: '2009/05/12', category: 'General' }]) } | |
| let(:site) { instance_double('Site', default_locale: :en, locales: [:en, :fr]) } | |
| let(:locale) { :en } | |
| - | let(:repository) { Locomotive::Steam::Repositories::Filesystem::ContentEntry.new(loader, site, locale) } |
| + | let(:content_type_repository) { instance_double('ContentTypeRepository') } |
| + | let(:repository) { Locomotive::Steam::Repositories::Filesystem::ContentEntry.new(loader, site, locale, content_type_repository) } |
| describe '#collection' do | |
| @@ | @@ -29,6 +30,20 @@ describe Locomotive::Steam::Repositories::Filesystem::ContentEntry do |
| end | |
| + | describe '#by_slug' do |
| + | |
| + | let(:slug) { nil } |
| + | subject { repository.by_slug(type, slug) } |
| + | |
| + | it { is_expected.to eq nil } |
| + | |
| + | context 'existing slug' do |
| + | let(:slug) { 'update-1' } |
| + | it { expect(subject.title).to eq({ en: 'Update #1', fr: 'Mise a jour #1' }) } |
| + | end |
| + | |
| + | end |
| + | |
| describe '#value_for' do | |
| let(:name) { :title } | |
| @@ | @@ -38,44 +53,66 @@ describe Locomotive::Steam::Repositories::Filesystem::ContentEntry do |
| it { is_expected.to eq 'Hello world' } | |
| - | context 'association' do |
| + | describe 'association do' do |
| - | # TODO |
| + | let(:author_type) { instance_double('AuthorType') } |
| + | let(:entry) { instance_double('Article', _slug: 'hello-world', author: association, authors: association) } |
| - | end |
| + | before do |
| + | allow(content_type_repository).to receive(:by_slug).with(:authors).and_return(:author_type) |
| + | end |
| - | end |
| + | context 'belongs_to association' do |
| - | describe '#all' do |
| + | let(:association) { instance_double('Association', type: :belongs_to, association: true, target_class_slug: :authors, target_slugs: ['john-doe'], order_by: nil) } |
| + | let(:name) { :author } |
| - | let(:conditions) { nil } |
| - | subject { repository.all(type, conditions) } |
| + | before do |
| + | expect(repository).to receive(:by_slug).with(:author_type, 'john-doe').and_return('John Doe') |
| + | end |
| - | it { expect(subject.size).to eq 1 } |
| + | it { expect(subject).to eq 'John Doe' } |
| - | end |
| + | end |
| + | |
| + | context 'has_many association' do |
| + | |
| + | let(:association) { instance_double('Association', type: :has_many, association: true, target_class_slug: :authors, target_field: :article, order_by: 'created_at') } |
| + | let(:name) { :authors } |
| + | |
| + | before do |
| + | allow(association).to receive(:source).and_return(entry) |
| + | expect(repository).to receive(:all).with(:author_type, { article: 'hello-world', order_by: 'created_at' }).and_return(%w(jane john)) |
| + | end |
| + | |
| + | it { expect(subject).to eq %w(jane john) } |
| + | |
| + | end |
| + | |
| + | context 'many_to_many association' do |
| - | # describe '#by_slug' do |
| + | let(:association) { instance_double('Association', type: :many_to_many, association: true, target_class_slug: :authors, target_slugs: %w(jane john), order_by: nil) } |
| + | let(:name) { :authors } |
| - | # let(:slug) { nil } |
| - | # subject { repository.by_slug(slug) } |
| + | before do |
| + | expect(repository).to receive(:all).with(:author_type, { '_slug.in' => %w(jane john) }).and_return(%w(jane john)) |
| + | end |
| - | # it { is_expected.to eq nil } |
| + | it { expect(subject).to eq %w(jane john) } |
| - | # context 'existing content type' do |
| + | end |
| - | # let(:slug) { 'articles' } |
| - | # it { expect(subject.name).to eq 'Articles' } |
| + | end |
| - | # end |
| + | end |
| - | # context 'slug is already a content type' do |
| + | describe '#all' do |
| - | # let(:slug) { instance_double('ContentType') } |
| - | # it { is_expected.to eq slug } |
| + | let(:conditions) { nil } |
| + | subject { repository.all(type, conditions) } |
| - | # end |
| + | it { expect(subject.size).to eq 1 } |
| - | # end |
| + | end |
| end | |
spec/unit/repositories/filesystem/models/content_entry_spec.rb
+11
-1
| @@ | @@ -91,7 +91,7 @@ describe Locomotive::Steam::Repositories::Filesystem::Models::ContentEntry do |
| end | |
| context 'a date time' do | |
| - | let(:field_type) { :date } |
| + | let(:field_type) { :date_time } |
| let(:value) { '2007/06/29 00:00:00' } | |
| let(:datetime) { DateTime.parse('2007/06/29 00:00:00') } | |
| it { is_expected.to eq datetime } | |
| @@ | @@ -101,6 +101,16 @@ describe Locomotive::Steam::Repositories::Filesystem::Models::ContentEntry do |
| end | |
| end | |
| + | context 'a file' do |
| + | let(:field_type) { :file } |
| + | let(:value) { 'foo.png' } |
| + | it { is_expected.to eq({ 'url' => 'foo.png' }) } |
| + | context 'localized' do |
| + | let(:value) { { en: 'foo-en.png', fr: 'foo-fr.png' } } |
| + | it { is_expected.to eq({ en: { 'url' => 'foo-en.png' }, fr: { 'url' => 'foo-fr.png' } }) } |
| + | end |
| + | end |
| + | |
| context 'a belongs_to relationship' do | |
| let(:field_type) { :belongs_to } | |
| let(:value) { 'john-doe' } | |
spec/unit/repositories/filesystem/models/content_type_spec.rb
+17
-0
| @@ | @@ -30,4 +30,21 @@ describe Locomotive::Steam::Repositories::Filesystem::Models::ContentType do |
| end | |
| + | describe '#order_by' do |
| + | |
| + | subject { content_type.order_by } |
| + | it { is_expected.to eq '_position asc' } |
| + | |
| + | context 'specifying manually' do |
| + | |
| + | before do |
| + | content_type.attributes[:order_by] = 'manually' |
| + | content_type.attributes[:order_direction] = 'desc' |
| + | end |
| + | it { is_expected.to eq '_position desc' } |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| end | |
spec/unit/repositories/filesystem/models/page_spec.rb
+22
-0
| @@ | @@ -0,0 +1,22 @@ |
| + | require 'spec_helper' |
| + | |
| + | describe Locomotive::Steam::Repositories::Filesystem::Models::Page do |
| + | |
| + | let(:attributes) { {} } |
| + | let(:page) { Locomotive::Steam::Repositories::Filesystem::Models::Page.new(attributes) } |
| + | |
| + | describe '#not_found' do |
| + | |
| + | let(:attributes) { { fullpath: { en: 'index' } } } |
| + | |
| + | subject { page.not_found? } |
| + | it { is_expected.to eq false } |
| + | |
| + | context 'true' do |
| + | let(:attributes) { { fullpath: { en: '404' } } } |
| + | it { is_expected.to eq true } |
| + | end |
| + | |
| + | end |
| + | |
| + | end |