content entry repository (only FS), some methods are still missing [WIP]
did
committed Feb 28, 2015
commit 66f2f77ca831980def1ae9f3e60dfd90d5602ac2
Showing 31
changed files with
1025 additions
and 571 deletions
locomotive/steam/adapters/filesystem.rb b/lib/locomotive/steam/adapters/filesystem.rb
+7
-3
| @@ | @@ -49,11 +49,15 @@ module Locomotive::Steam |
| end | |
| def memoized_dataset(mapper, scope) | |
| - | cache.fetch(mapper.name) do |
| + | cache.fetch(cache_key(mapper, scope)) do |
| dataset(mapper, scope) | |
| end | |
| end | |
| + | def cache_key(mapper, scope) |
| + | "#{scope.to_key}_#{mapper.name}" |
| + | end |
| + | |
| def dataset(mapper, scope) | |
| Locomotive::Steam::Adapters::Memory::Dataset.new(mapper.name).tap do |dataset| | |
| populate_dataset(dataset, mapper, scope) | |
| @@ | @@ -78,7 +82,7 @@ module Locomotive::Steam |
| end | |
| def build_yaml_loaders | |
| - | %i(sites pages content_types snippets translations theme_assets).inject({}) do |memo, name| |
| + | %i(sites pages content_types content_entries snippets translations theme_assets).inject({}) do |memo, name| |
| memo[name] = build_klass('YAMLLoaders', name).new(site_path) | |
| memo | |
| end | |
| @@ | @@ -86,7 +90,7 @@ module Locomotive::Steam |
| def build_sanitizers | |
| hash = Hash.new { build_klass('Sanitizers', :simple).new } | |
| - | %i(pages content_types snippets).inject(hash) do |memo, name| |
| + | %i(pages content_types content_entries snippets).inject(hash) do |memo, name| |
| memo[name] = build_klass('Sanitizers', name).new | |
| memo | |
| end | |
locomotive/steam/adapters/filesystem/sanitizers/content_entry.rb b/lib/locomotive/steam/adapters/filesystem/sanitizers/content_entry.rb
+73
-0
| @@ | @@ -1,3 +1,76 @@ |
| + | module Locomotive::Steam |
| + | module Adapters |
| + | module Filesystem |
| + | module Sanitizers |
| + | |
| + | class ContentEntry |
| + | |
| + | include Adapters::Filesystem::Sanitizer |
| + | |
| + | def apply_to_entity(entity) |
| + | super |
| + | add_label(entity) |
| + | end |
| + | |
| + | def apply_to_dataset(dataset) |
| + | dataset.all.each do |entry| |
| + | set_slug(entry, dataset) |
| + | end |
| + | end |
| + | |
| + | private |
| + | |
| + | def add_label(entry) |
| + | value = entry.attributes.delete(:_label) |
| + | name = entry.content_type.label_field_name |
| + | |
| + | if entry.attributes[name].respond_to?(:translations) # localized? |
| + | entry.attributes[name][default_locale] = value |
| + | else |
| + | entry.attributes[name] ||= value |
| + | end |
| + | end |
| + | |
| + | def set_slug(entry, dataset) |
| + | if entry._label.respond_to?(:translations) # localized? |
| + | entry[:_slug] ||= {} |
| + | entry._label.each do |locale, label| |
| + | entry[:_slug][locale] ||= slugify(label, dataset, locale) |
| + | end |
| + | else |
| + | entry[:_slug] ||= slugify(entry._label, dataset) |
| + | end |
| + | end |
| + | |
| + | def slugify(label, dataset, locale = nil) |
| + | base, index = label.permalink(false), nil |
| + | _slugify = -> (i) { [base, i].compact.join('-') } |
| + | |
| + | while !is_slug_unique?(_slugify.call(index), dataset, locale) |
| + | index = index ? index + 1 : 1 |
| + | end |
| + | |
| + | _slugify.call(index) |
| + | end |
| + | |
| + | def is_slug_unique?(slug, dataset, locale) |
| + | dataset.query(locale) { where(_slug: slug) }.first.nil? |
| + | # Filesystem::MemoryAdapter::Query.new(collection, locale) do |
| + | # where(_slug: slug) |
| + | # end.first.nil? |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
| + | end |
| + | |
| + | |
| + | |
| + | |
| + | |
| # module Locomotive | |
| # module Steam | |
| # module Repositories | |
locomotive/steam/adapters/filesystem/yaml_loaders/content_entry.rb b/lib/locomotive/steam/adapters/filesystem/yaml_loaders/content_entry.rb
+50
-51
| @@ | @@ -1,51 +1,50 @@ |
| - | # module Locomotive |
| - | # module Steam |
| - | # module Repositories |
| - | # module Filesystem |
| - | # module YAMLLoaders |
| - | |
| - | # class ContentEntry < Struct.new(:root_path, :cache) |
| - | |
| - | # include YAMLLoaders::Concerns::Common |
| - | |
| - | # def list_of_attributes(content_type) |
| - | # cache.fetch("data/#{content_type.slug}") { load_list(content_type) } |
| - | # end |
| - | |
| - | # def write(content_type, attributes) |
| - | # list = cache.read("data/#{content_type.slug}") |
| - | |
| - | # list << attributes.merge(content_type: content_type) |
| - | # end |
| - | |
| - | # private |
| - | |
| - | # def load_list(content_type) |
| - | # [].tap do |list| |
| - | # each(content_type.slug) do |label, attributes, position| |
| - | # default = { content_type: content_type, _position: position, _label: label.to_s } |
| - | # list << default.merge(attributes) |
| - | # end |
| - | # end |
| - | # end |
| - | |
| - | # def each(slug, &block) |
| - | # position = 0 |
| - | # load(File.join(path, "#{slug}.yml")).each do |element| |
| - | # label, attributes = element.keys.first, element.values.first |
| - | # yield(label, attributes, position) |
| - | # position += 1 |
| - | # end |
| - | # end |
| - | |
| - | # def path |
| - | # File.join(root_path, 'data') |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
| - | # end |
| - | # end |
| - | # end |
| - | # end |
| + | module Locomotive |
| + | module Steam |
| + | module Adapters |
| + | module Filesystem |
| + | module YAMLLoaders |
| + | |
| + | class ContentEntry |
| + | |
| + | include Adapters::Filesystem::YAMLLoader |
| + | |
| + | def load(scope) |
| + | super |
| + | load_list |
| + | end |
| + | |
| + | private |
| + | |
| + | def load_list |
| + | [].tap do |list| |
| + | each(content_type_slug) do |label, attributes, position| |
| + | default = { _position: position, _label: label.to_s } |
| + | list << default.merge(attributes) |
| + | end |
| + | end |
| + | end |
| + | |
| + | def each(slug, &block) |
| + | position = 0 |
| + | _load(File.join(path, "#{slug}.yml")).each do |element| |
| + | label, attributes = element.keys.first, element.values.first |
| + | yield(label, attributes, position) |
| + | position += 1 |
| + | end |
| + | end |
| + | |
| + | def path |
| + | File.join(site_path, 'data') |
| + | end |
| + | |
| + | def content_type_slug |
| + | @scope.context[:content_type].slug |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
| + | end |
| + | end |
locomotive/steam/adapters/memory/dataset.rb b/lib/locomotive/steam/adapters/memory/dataset.rb
+3
-3
| @@ | @@ -55,9 +55,9 @@ module Locomotive::Steam |
| !!id && records.has_key?(id) | |
| end | |
| - | # def query |
| - | # Query.new(self) |
| - | # end |
| + | def query(locale = nil, &block) |
| + | Query.new(self, locale, &block) |
| + | end |
| def clear! | |
| @records = {} | |
locomotive/steam/entities/content_entry.rb b/lib/locomotive/steam/entities/content_entry.rb
+139
-0
| @@ | @@ -0,0 +1,139 @@ |
| + | require 'chronic' |
| + | |
| + | module Locomotive::Steam |
| + | |
| + | class ContentEntry |
| + | |
| + | ASSOCIATION_NAMES = [:belongs_to, :has_many, :many_to_many].freeze |
| + | |
| + | include Locomotive::Steam::Models::Entity |
| + | |
| + | attr_accessor :content_type |
| + | |
| + | def initialize(attributes = {}) |
| + | super({ |
| + | _visible: true, |
| + | _position: 0, |
| + | created_at: Time.now, |
| + | updated_at: Time.now |
| + | }.merge(attributes)) |
| + | end |
| + | |
| + | def _visible?; !!self[:_visible]; end |
| + | alias :visible? :_visible? |
| + | |
| + | def _slug; self[:_slug]; end |
| + | alias :_permalink :_slug |
| + | |
| + | def method_missing(name, *args, &block) |
| + | if is_dynamic_attribute?(name) |
| + | cast_value(name) |
| + | elsif attributes.include?(name) |
| + | self[name] |
| + | else |
| + | super |
| + | end |
| + | end |
| + | |
| + | def valid? |
| + | errors.clear |
| + | content_type.fields.required.each do |field| |
| + | errors.add_on_blank(field.name.to_sym) |
| + | end |
| + | errors.empty? |
| + | end |
| + | |
| + | def content_type |
| + | @content_type || attributes[:content_type] |
| + | end |
| + | |
| + | def content_type_slug |
| + | content_type.slug |
| + | end |
| + | |
| + | def _label |
| + | self[content_type.label_field_name] |
| + | end |
| + | |
| + | # def localized_attributes |
| + | # self.class.localized_attributes + content_type.localized_fields_names |
| + | # end |
| + | |
| + | def to_liquid |
| + | Locomotive::Steam::Liquid::Drops::ContentEntry.new(self) |
| + | end |
| + | |
| + | private |
| + | |
| + | def is_dynamic_attribute?(name) |
| + | content_type.fields_by_name.has_key?(name) |
| + | end |
| + | |
| + | def cast_value(name) |
| + | field = content_type.fields_by_name[name] |
| + | |
| + | begin |
| + | _cast_value(field) |
| + | rescue Exception => e |
| + | Locomotive::Common::Logger.info "[#{content_type.slug}][#{_label}] Unable to cast the \"#{name}\" field, reason: #{e.message}".yellow |
| + | nil |
| + | end |
| + | end |
| + | |
| + | def _cast_value(field) |
| + | if ASSOCIATION_NAMES.include?(field.type) |
| + | AssociationMetadata.new(field.type, self, field, [*attributes[field.name]]) |
| + | elsif private_methods.include?(:"_cast_#{field.type}") |
| + | send(:"_cast_#{field.type}", field.name) |
| + | else |
| + | attributes[field.name] |
| + | end |
| + | end |
| + | |
| + | def _cast_integer(name) |
| + | _cast_convertor(name, &:to_i) |
| + | end |
| + | |
| + | def _cast_float(name) |
| + | _cast_convertor(name, &:to_f) |
| + | end |
| + | |
| + | def _cast_file(name) |
| + | _cast_convertor(name) do |value| |
| + | value.present? ? { 'url' => value } : nil |
| + | end |
| + | end |
| + | |
| + | def _cast_date(name) |
| + | _cast_time(name, :to_date) |
| + | end |
| + | |
| + | def _cast_date_time(name) |
| + | _cast_time(name, :to_date) |
| + | end |
| + | |
| + | def _cast_time(name, end_method) |
| + | _cast_convertor(name) do |value| |
| + | value.is_a?(String) ? Chronic.parse(value).send(end_method) : value |
| + | end |
| + | end |
| + | |
| + | def _cast_convertor(name, &block) |
| + | if (value = attributes[name]).respond_to?(:translations) |
| + | value.each { |l, _value| value[l] = yield(_value) } |
| + | value |
| + | else |
| + | yield(value) |
| + | end |
| + | end |
| + | |
| + | 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 |
| + | |
| + | end |
locomotive/steam/entities/content_type.rb b/lib/locomotive/steam/entities/content_type.rb
+11
-0
| @@ | @@ -16,6 +16,17 @@ module Locomotive::Steam |
| self.entries_custom_fields | |
| end | |
| + | def fields_by_name |
| + | @fields_by_name ||= (fields.all.inject({}) do |memo, field| |
| + | memo[field.name] = field |
| + | memo |
| + | end) |
| + | end |
| + | |
| + | def localized_fields_names |
| + | self.fields.localized_fields_names |
| + | end |
| + | |
| def label_field_name | |
| (self[:label_field_name] || fields.first.name).to_sym | |
| end | |
locomotive/steam/models/i18n_field.rb b/lib/locomotive/steam/models/i18n_field.rb
+4
-0
| @@ | @@ -27,6 +27,10 @@ module Locomotive::Steam |
| @translations.values | |
| end | |
| + | def each(&block) |
| + | @translations.each(&block) |
| + | end |
| + | |
| end | |
| end | |
locomotive/steam/models/mapper.rb b/lib/locomotive/steam/models/mapper.rb
+0
-1
| @@ | @@ -72,7 +72,6 @@ module Locomotive::Steam |
| def set_default_attributes(entity) | |
| @default_attributes.each do |(name, value)| | |
| - | # _value = value.respond_to?(:call) ? @repository.instance_eval(&value) : value |
| _value = value.respond_to?(:call) ? value.call(@repository) : value | |
| entity.send(:"#{name}=", _value) | |
| end | |
locomotive/steam/models/repository.rb b/lib/locomotive/steam/models/repository.rb
+5
-3
| @@ | @@ -35,11 +35,13 @@ module Locomotive::Steam |
| alias :all :query | |
| - | def mapper |
| + | def mapper(memoized = true) |
| name, options, block = mapper_options | |
| - | @mapper ||= Mapper.new(name, options, self, &block) |
| - | end |
| + | return @mapper if @mapper && memoized |
| + | |
| + | @mapper = Mapper.new(name, options, self, &block) |
| + | end |
| # TODO: not sure about that. could it be used further in the dev | |
| # def collection_name | |
locomotive/steam/models/scope.rb b/lib/locomotive/steam/models/scope.rb
+15
-1
| @@ | @@ -1,7 +1,13 @@ |
| module Locomotive::Steam | |
| module Models | |
| - | class Scope < Struct.new(:site, :locale) |
| + | class Scope |
| + | |
| + | attr_accessor :site, :locale, :context |
| + | |
| + | def initialize(site, locale, context = nil) |
| + | @site, @locale, @context = site, locale, (context || {}) |
| + | end |
| def default_locale | |
| site.try(:default_locale) | |
| @@ | @@ -11,6 +17,14 @@ module Locomotive::Steam |
| site.try(:locales) | |
| end | |
| + | def to_key |
| + | ['site', @site.try(:_id)].tap do |base| |
| + | @context.each do |name, object| |
| + | base << [name, object.try(:_id)] |
| + | end |
| + | end.flatten.join('_') |
| + | end |
| + | |
| end | |
| end | |
locomotive/steam/repositories/content_entry_repository.rb b/lib/locomotive/steam/repositories/content_entry_repository.rb
+200
-0
| @@ | @@ -0,0 +1,200 @@ |
| + | module Locomotive |
| + | module Steam |
| + | |
| + | class ContentEntryRepository |
| + | |
| + | include Models::Repository |
| + | |
| + | attr_accessor :content_type |
| + | |
| + | def initialize(adapter, site = nil, locale = nil, content_type_repository = nil) |
| + | @adapter = adapter |
| + | @scope = Locomotive::Steam::Models::Scope.new(site, locale) |
| + | @content_type_repository = content_type_repository |
| + | end |
| + | |
| + | # Entity mapping |
| + | mapping :content_entries, entity: ContentEntry do |
| + | localized_attributes :_slug, :seo_title, :meta_description, :meta_keywords |
| + | |
| + | default_attribute :content_type, -> (repository) { repository.content_type } |
| + | |
| + | # # embedded association |
| + | # association :entries_custom_fields, ContentTypeFieldRepository |
| + | end |
| + | |
| + | def all(type, conditions = {}) |
| + | conditions = { _visible: true }.merge(conditions || {}) |
| + | |
| + | self.content_type = type # used for creating the scope |
| + | self.scope.context[:content_type] = type |
| + | |
| + | # filter the entries by the content type they belong to |
| + | conditions[:content_type_id] = type._id |
| + | |
| + | # 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)|| conditions.delete('order_by') || type.order_by |
| + | |
| + | query { where(conditions).order_by(order_by) }.all |
| + | end |
| + | |
| + | # Engine: content_type.entries.build(attributes) |
| + | def build(type, attributes = {}) |
| + | raise 'TODO, delegate to the adapter' |
| + | |
| + | # collection_options[:model].new(attributes).tap do |entry| |
| + | # # set the reference to the content type |
| + | # entry.content_type = type |
| + | # end |
| + | end |
| + | |
| + | # Engine: entry.save |
| + | def persist(entry) |
| + | return nil if entry.nil? |
| + | |
| + | raise 'TODO, delegate to the adapter' |
| + | |
| + | # collection = memoized_collection(entry.content_type) |
| + | |
| + | # # slugify entry |
| + | # sanitizer.set_slug(entry, collection) |
| + | |
| + | # collection << entry # immediate result |
| + | |
| + | # # make sure we write it back to the data source |
| + | # loader.write(entry.content_type, entry.attributes) |
| + | end |
| + | |
| + | # Engine: all(conditions).count > 0 |
| + | def exists?(type, conditions = {}) |
| + | query(type) { where(conditions) }.all.size > 0 |
| + | end |
| + | |
| + | # Engine: not necessary |
| + | def by_slug(type, slug) |
| + | query(type) { where(_slug: slug) }.first |
| + | end |
| + | |
| + | # Engine: entry.send(:name) :-) |
| + | def value_for(name, entry, conditions = {}) |
| + | value = entry.send(name) |
| + | |
| + | if value.respond_to?(:association) |
| + | association(value, conditions || {}) |
| + | else |
| + | value |
| + | end |
| + | end |
| + | |
| + | # Engine: entry.next |
| + | def next(entry) |
| + | next_or_previous(entry, 'gt', 'lt') |
| + | end |
| + | |
| + | # Engine: entry.previous |
| + | def previous(entry) |
| + | next_or_previous(entry, 'lt', 'gt') |
| + | end |
| + | |
| + | # Engine: content_type.entries.klass.send(:group_by_select_option, name, content_type.order_by_definition) |
| + | def group_by_select_option(type, name) |
| + | return {} if type.nil? || name.nil? || type.fields_by_name[name].type != :select |
| + | |
| + | raise 'TODO: implement the group_by method' |
| + | _groups = all(type).group_by(&name) |
| + | |
| + | groups = content_type_repository.select_options(type, name).map do |option| |
| + | { name: option, entries: _groups.delete(option) || [] } |
| + | end |
| + | |
| + | unless _groups.blank? |
| + | groups << (empty = { name: nil, entries: [] }) |
| + | _groups.values.each { |list| empty[:entries] += list } |
| + | end |
| + | |
| + | groups |
| + | end |
| + | |
| + | private |
| + | |
| + | def scoped_query(type, &block) |
| + | self.content_type = type |
| + | query(&block) |
| + | end |
| + | |
| + | def mapper(memoized = true) |
| + | super(false).tap do |mapper| |
| + | unless self.content_type.localized_fields_names.blank? |
| + | mapper.localized_attributes(*self.content_type.localized_fields_names) |
| + | end |
| + | end |
| + | end |
| + | |
| + | def type_from(slug) |
| + | content_type_repository.by_slug(slug) |
| + | end |
| + | |
| + | def localized_slug(entry) |
| + | raise 'SHOULD NOT BE USED' |
| + | localized_attribute(entry, :_slug) |
| + | 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 ||= {} |
| + | |
| + | # return @collections[slug] if @collections[slug] |
| + | |
| + | # @collections[slug] = collection(content_type) |
| + | # end |
| + | |
| + | def next_or_previous(entry, asc_op, desc_op) |
| + | return nil if entry.nil? |
| + | |
| + | type = entry.content_type |
| + | column, direction = type.order_by.split |
| + | operator = direction == 'asc' ? asc_op : desc_op |
| + | value = localized_attribute(entry, column) |
| + | |
| + | query(type) { where("#{column}.#{operator}" => value) }.first |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
locomotive/steam/repositories/content_type_field_repository.rb b/lib/locomotive/steam/repositories/content_type_field_repository.rb
+4
-0
| @@ | @@ -22,6 +22,10 @@ module Locomotive |
| end | |
| end | |
| + | def required |
| + | query { where(required: true) }.all |
| + | end |
| + | |
| def localized_names | |
| query { where(localized: true) }.all.map(&:name) | |
| end | |
locomotive/steam/repositories/filesystem/content_entry.rb b/lib/locomotive/steam/repositories/filesystem/content_entry.rb
+1
-1
| @@ | @@ -56,7 +56,7 @@ |
| # query(type) { where(_slug: slug) }.first | |
| # end | |
| - | # # Engine: entry.name :-) |
| + | # # Engine: entry.send(:name) :-) |
| # def value_for(name, entry, conditions = {}) | |
| # value = entry.send(name) | |
spec/unit/adapters/filesystem/yaml_loaders/content_entry_spec.rb
+18
-15
| @@ | @@ -1,22 +1,25 @@ |
| - | # require 'spec_helper' |
| + | require 'spec_helper' |
| - | # describe Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::ContentEntry do |
| + | require_relative '../../../../../lib/locomotive/steam/adapters/filesystem/yaml_loader.rb' |
| + | require_relative '../../../../../lib/locomotive/steam/adapters/filesystem/yaml_loaders/content_entry.rb' |
| - | # let(:root_path) { default_fixture_site_path } |
| - | # let(:cache) { NoCacheStore.new } |
| - | # let(:content_type) { instance_double('Articles', slug: 'bands') } |
| - | # let(:loader) { Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::ContentEntry.new(root_path, cache) } |
| + | describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do |
| - | # describe '#list_of_attributes' do |
| + | let(:site_path) { default_fixture_site_path } |
| + | let(:content_type) { instance_double('Articles', slug: 'bands') } |
| + | let(:scope) { instance_double('Scope', locale: :en, context: { content_type: content_type }) } |
| + | let(:loader) { described_class.new(site_path) } |
| - | # subject { loader.list_of_attributes(content_type).sort { |a, b| a[:_label] <=> b[:_label] } } |
| + | describe '#load' do |
| - | # it 'tests various stuff' do |
| - | # expect(subject.size).to eq 3 |
| - | # expect(subject.first[:_label]).to eq 'Alice in Chains' |
| - | # expect(subject.first[:content_type]).to eq content_type |
| - | # end |
| + | subject { loader.load(scope).sort { |a, b| a[:_label] <=> b[:_label] } } |
| - | # end |
| + | it 'tests various stuff' do |
| + | expect(subject.size).to eq 3 |
| + | expect(subject.first[:_label]).to eq 'Alice in Chains' |
| + | expect(subject.first[:content_type]).to eq nil |
| + | end |
| - | # end |
| + | end |
| + | |
| + | end |
spec/unit/adapters/filesystem_adapter_spec.rb
+1
-1
| @@ | @@ -5,7 +5,7 @@ require_relative '../../../lib/locomotive/steam/adapters/filesystem.rb' |
| describe Locomotive::Steam::FilesystemAdapter do | |
| let(:mapper) { instance_double('Mapper', name: :test) } | |
| - | let(:scope) { instance_double('Scope', site: site, locale: nil) } |
| + | let(:scope) { instance_double('Scope', site: site, locale: nil, to_key: 'key') } |
| let(:adapter) { Locomotive::Steam::FilesystemAdapter.new(nil) } | |
| describe '#key' do | |
spec/unit/entities/content_entry_spec.rb
+186
-0
| @@ | @@ -0,0 +1,186 @@ |
| + | require 'spec_helper' |
| + | |
| + | describe Locomotive::Steam::ContentEntry do |
| + | |
| + | let(:fields) { nil } |
| + | let(:repository) { instance_double('FieldRepository', all: fields) } |
| + | let(:type) { instance_double('ContentType', slug: 'articles', label_field_name: :title, fields: repository) } |
| + | let(:attributes) { { title: 'Hello world', _slug: 'hello-world' } } |
| + | let(:content_entry) { described_class.new(attributes) } |
| + | |
| + | before { content_entry.content_type = type } |
| + | |
| + | describe '#valid?' do |
| + | |
| + | let(:fields) { [instance_double('Field', name: :title, type: 'string', required: true)] } |
| + | |
| + | before do |
| + | allow(repository).to receive(:required).and_return(fields) |
| + | allow(type).to receive(:fields_by_name).and_return({ title: fields.first }) |
| + | end |
| + | |
| + | subject { content_entry.valid? } |
| + | it { is_expected.to eq true } |
| + | |
| + | context 'missing attribute' do |
| + | |
| + | let(:attributes) { {} } |
| + | it { is_expected.to eq false } |
| + | it { subject; expect(content_entry.errors[:title]).to eq(["can't not be blank"]) } |
| + | it { subject; expect(content_entry.errors.empty?).to eq false } |
| + | |
| + | end |
| + | |
| + | describe 'adding a custom error message' do |
| + | |
| + | before { content_entry.errors.add(:title, 'is mandatory') } |
| + | |
| + | it { expect(content_entry.errors[:title]).to eq(['is mandatory']) } |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | describe '#_label' do |
| + | |
| + | subject { content_entry._label } |
| + | it { is_expected.to eq 'Hello world' } |
| + | |
| + | end |
| + | |
| + | describe '#content_type_slug' do |
| + | |
| + | subject { content_entry.content_type_slug } |
| + | it { is_expected.to eq 'articles' } |
| + | |
| + | end |
| + | |
| + | # describe '#localized_attributes' do |
| + | |
| + | # subject { content_entry.localized_attributes } |
| + | # it { is_expected.to include :seo_title } |
| + | # it { is_expected.to include :title } |
| + | # it { is_expected.to include :_slug } |
| + | |
| + | # end |
| + | |
| + | describe 'dynamic attributes' do |
| + | |
| + | let(:field_type) { :string } |
| + | let(:attributes) { { my_field: value } } |
| + | let(:field) { instance_double('Field', name: :my_field, type: field_type) } |
| + | |
| + | before { allow(type).to receive(:fields_by_name).and_return(my_field: field) } |
| + | |
| + | subject { content_entry.my_field } |
| + | |
| + | describe 'unable to cast it' do |
| + | |
| + | let(:field_type) { :float } |
| + | let(:value) { [] } |
| + | it { is_expected.to eq nil } |
| + | |
| + | end |
| + | |
| + | context 'no provided value, should return nil' do |
| + | |
| + | let(:attributes) { {} } |
| + | it { is_expected.to eq nil } |
| + | |
| + | end |
| + | |
| + | context 'a string' do |
| + | let(:value) { 'Hello world' } |
| + | it { is_expected.to eq 'Hello world' } |
| + | context 'localized' do |
| + | let(:value) { build_i18n_field(en: 'Hello world', fr: 'Bonjour monde') } |
| + | it { expect(subject.translations).to eq('en' => 'Hello world', 'fr' => 'Bonjour monde') } |
| + | end |
| + | end |
| + | |
| + | context 'an integer' do |
| + | let(:field_type) { :integer } |
| + | let(:value) { '42' } |
| + | it { is_expected.to eq 42 } |
| + | context 'localized' do |
| + | let(:value) { build_i18n_field(en: 42, fr: '42') } |
| + | it { expect(subject.translations).to eq('en' => 42, 'fr' => 42) } |
| + | end |
| + | end |
| + | |
| + | context 'a float' do |
| + | let(:field_type) { :float } |
| + | let(:value) { '42.0' } |
| + | it { is_expected.to eq 42.0 } |
| + | context 'localized' do |
| + | let(:value) { build_i18n_field(en: 42.0, fr: '42.0') } |
| + | it { expect(subject.translations).to eq('en' => 42.0, 'fr' => 42.0) } |
| + | end |
| + | end |
| + | |
| + | context 'a date' do |
| + | let(:field_type) { :date } |
| + | let(:value) { '2007/06/29' } |
| + | let(:date) { Date.parse('2007/06/29') } |
| + | it { is_expected.to eq date } |
| + | context 'localized' do |
| + | let(:value) { build_i18n_field(en: '2007/06/29', fr: date) } |
| + | it { expect(subject.translations).to eq('en' => date, 'fr' => date) } |
| + | end |
| + | end |
| + | |
| + | context 'a date time' do |
| + | 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 } |
| + | context 'localized' do |
| + | let(:value) { build_i18n_field(en: '2007/06/29 00:00:00', fr: datetime) } |
| + | it { expect(subject.translations).to eq('en' => datetime, 'fr' => datetime) } |
| + | 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) { build_i18n_field(en: 'foo-en.png', fr: 'foo-fr.png') } |
| + | it { expect(subject.translations).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' } |
| + | it { expect(subject.type).to eq :belongs_to } |
| + | it { expect(subject.target_slugs).to eq ['john-doe'] } |
| + | it { expect(subject.source).to eq content_entry } |
| + | it { expect(subject.field).to eq field } |
| + | end |
| + | |
| + | context 'a has_many relationship' do |
| + | let(:field_type) { :has_many } |
| + | let(:value) { nil } |
| + | it { expect(subject.type).to eq :has_many } |
| + | it { expect(subject.target_slugs).to eq [] } |
| + | it { expect(subject.source).to eq content_entry } |
| + | it { expect(subject.field).to eq field } |
| + | end |
| + | |
| + | context 'a many_to_many relationship' do |
| + | let(:field_type) { :many_to_many } |
| + | let(:value) { ['john-doe', 'jane-doe'] } |
| + | it { expect(subject.type).to eq :many_to_many } |
| + | it { expect(subject.target_slugs).to eq ['john-doe', 'jane-doe'] } |
| + | it { expect(subject.source).to eq content_entry } |
| + | it { expect(subject.field).to eq field } |
| + | end |
| + | |
| + | end |
| + | |
| + | def build_i18n_field(translations = {}) |
| + | Locomotive::Steam::Models::I18nField.new(:my_field, translations) |
| + | end |
| + | |
| + | end |
spec/unit/entities/content_type_spec.rb
+9
-1
| @@ | @@ -3,9 +3,10 @@ require 'spec_helper' |
| describe Locomotive::Steam::ContentType do | |
| let(:fields) { [instance_double('Field1', label: 'Title', name: :title, localized: false), instance_double('Field2', label: 'Author', name: :author, localized: true)] } | |
| + | let(:repository) { instance_double('FieldRepository', all: fields, first: fields.first) } |
| let(:content_type) { described_class.new(name: 'Articles') } | |
| - | before { allow(content_type).to receive(:fields).and_return(fields) } |
| + | before { allow(content_type).to receive(:fields).and_return(repository) } |
| describe '#label_field_name' do | |
| @@ | @@ -21,6 +22,13 @@ describe Locomotive::Steam::ContentType do |
| end | |
| + | describe '#fields_by_name' do |
| + | |
| + | subject { content_type.fields_by_name } |
| + | it { expect(subject.keys).to eq [:title, :author] } |
| + | |
| + | end |
| + | |
| # describe '#localized_fields_names' do | |
| # subject { content_type.localized_fields_names } | |
spec/unit/entities/page_spec.rb
+1
-1
| @@ | @@ -3,7 +3,7 @@ require 'spec_helper' |
| describe Locomotive::Steam::Page do | |
| let(:attributes) { {} } | |
| - | let(:page) { Locomotive::Steam::Page.new(attributes) } |
| + | let(:page) { described_class.new(attributes) } |
| describe '#index?' do | |
spec/unit/entities/site_spec.rb
+1
-1
| @@ | @@ -3,7 +3,7 @@ require 'spec_helper' |
| describe Locomotive::Steam::Site do | |
| let(:attributes) { {} } | |
| - | let(:site) { Locomotive::Steam::Site.new(attributes) } |
| + | let(:site) { described_class.new(attributes) } |
| describe '#handle' do | |
spec/unit/models/mapper_spec.rb
+1
-1
| @@ | @@ -6,7 +6,7 @@ describe Locomotive::Steam::Models::Mapper do |
| let(:name) { 'pages' } | |
| let(:options) { { entity: MyPage } } | |
| let(:block) { nil } | |
| - | let(:mapper) { Locomotive::Steam::Models::Mapper.new(name, options, repository, &block) } |
| + | let(:mapper) { described_class.new(name, options, repository, &block) } |
| describe '#localized attributes' do | |
spec/unit/models/scope_spec.rb
+27
-0
| @@ | @@ -0,0 +1,27 @@ |
| + | require 'spec_helper' |
| + | |
| + | describe Locomotive::Steam::Models::Scope do |
| + | |
| + | let(:site) { instance_double('Site', _id: 1) } |
| + | let(:locale) { :en } |
| + | let(:context) { nil } |
| + | let(:scope) { described_class.new(site, locale, context) } |
| + | |
| + | describe '#to_key' do |
| + | |
| + | subject { scope.to_key } |
| + | |
| + | it { is_expected.to eq 'site_1' } |
| + | |
| + | context 'with a content type for instance' do |
| + | |
| + | let(:content_type) { instance_double('ContentType', _id: 42) } |
| + | let(:context) { { content_type: content_type } } |
| + | |
| + | it { is_expected.to eq 'site_1_content_type_42' } |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
spec/unit/repositories/content_entry_repository_spec.rb
+269
-0
| @@ | @@ -0,0 +1,269 @@ |
| + | require 'spec_helper' |
| + | |
| + | require_relative '../../../lib/locomotive/steam/adapters/filesystem.rb' |
| + | |
| + | describe Locomotive::Steam::ContentEntryRepository do |
| + | |
| + | let(:type) { instance_double('Articles', _id: 1, slug: 'articles', order_by: nil, label_field_name: :title, localized_fields_names: [:title], fields_by_name: { title: instance_double('Field', name: :title, type: :string) }) } |
| + | let(:entries) { [{ content_type_id: 1, _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(:locale) { :en } |
| + | let(:site) { instance_double('Site', _id: 1, default_locale: :en, locales: %i(en fr)) } |
| + | let(:adapter) { Locomotive::Steam::FilesystemAdapter.new(nil) } |
| + | |
| + | let(:content_type_repository) { instance_double('ContentTypeRepository') } |
| + | let(:repository) { described_class.new(adapter, site, locale, content_type_repository) } |
| + | |
| + | before do |
| + | allow(adapter).to receive(:collection).and_return(entries) |
| + | adapter.cache = NoCacheStore.new |
| + | end |
| + | |
| + | describe '#all' do |
| + | |
| + | let(:conditions) { nil } |
| + | |
| + | subject { repository.all(type, conditions) } |
| + | |
| + | it { expect(subject.size).to eq 1 } |
| + | |
| + | describe 'first element' do |
| + | |
| + | subject { repository.all(type, conditions).first } |
| + | |
| + | it { expect(subject.class).to eq Locomotive::Steam::ContentEntry } |
| + | it { expect(subject._label.translations).to eq('en' => 'Update #1', 'fr' => 'Mise a jour #1') } |
| + | it { expect(subject._slug.translations).to eq('en' => 'update-number-1', 'fr' => 'mise-a-jour-number-1') } |
| + | it { expect(subject.title.translations).to eq('en' => 'Update #1', 'fr' => 'Mise a jour #1') } |
| + | it { expect(subject.content_type).to eq type } |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | # describe '#build' do |
| + | |
| + | # let(:attributes) { { title: 'Hello world' } } |
| + | # subject { repository.build(type, attributes) } |
| + | |
| + | # it { expect(subject.title).to eq 'Hello world' } |
| + | |
| + | # end |
| + | |
| + | # describe '#persist' do |
| + | |
| + | # let(:entry) { instance_double('NewEntry', _visible: true, content_type: type, _label: 'Hello world', attributes: { title: 'Hello world' }) } |
| + | # subject { repository.persist(entry) } |
| + | |
| + | # before do |
| + | # expect(entry).to receive(:[]).with(:_slug).and_return(nil) |
| + | # expect(entry).to receive(:[]=).with(:_slug, 'hello-world') |
| + | # expect(loader).to receive(:write).with(type, { title: 'Hello world' }) |
| + | # end |
| + | |
| + | # it { expect { subject }.to change { repository.all(type).size }.by(1) } |
| + | |
| + | # end |
| + | |
| + | # describe '#exists?' do |
| + | |
| + | # let(:conditions) { {} } |
| + | # subject { repository.exists?(type, conditions) } |
| + | |
| + | # it { expect(subject).to eq true } |
| + | |
| + | # context 'more specific conditions' do |
| + | |
| + | # let(:conditions) { { '_slug' => 'update-number-1' } } |
| + | # it { expect(subject).to eq true } |
| + | |
| + | # end |
| + | |
| + | # context 'conditions which do match any entries' do |
| + | |
| + | # let(:conditions) { { '_slug' => 'foo' } } |
| + | # it { expect(subject).to eq false } |
| + | |
| + | # end |
| + | |
| + | # 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-number-1' } |
| + | # it { expect(subject.title).to eq({ en: 'Update #1', fr: 'Mise a jour #1' }) } |
| + | # end |
| + | |
| + | # end |
| + | |
| + | # describe '#value_for' do |
| + | |
| + | # let(:name) { :title } |
| + | # let(:entry) { instance_double('Article', title: 'Hello world') } |
| + | |
| + | # subject { repository.value_for(name, entry) } |
| + | |
| + | # it { is_expected.to eq 'Hello world' } |
| + | |
| + | # describe 'association do' do |
| + | |
| + | # let(:author_type) { instance_double('AuthorType') } |
| + | # let(:entry) { instance_double('Article', _slug: 'hello-world', author: association, authors: association) } |
| + | |
| + | # before do |
| + | # allow(content_type_repository).to receive(:by_slug).with(:authors).and_return(:author_type) |
| + | # end |
| + | |
| + | # context 'belongs_to association' 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 } |
| + | |
| + | # before do |
| + | # expect(repository).to receive(:by_slug).with(:author_type, 'john-doe').and_return('John Doe') |
| + | # end |
| + | |
| + | # it { expect(subject).to eq 'John Doe' } |
| + | |
| + | # 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 |
| + | |
| + | # 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 } |
| + | |
| + | # before do |
| + | # expect(repository).to receive(:all).with(:author_type, { '_slug.in' => %w(jane john) }).and_return(%w(jane john)) |
| + | # end |
| + | |
| + | # it { expect(subject).to eq %w(jane john) } |
| + | |
| + | # end |
| + | |
| + | # end |
| + | |
| + | # end |
| + | |
| + | # describe '#next' do |
| + | |
| + | # let(:type) { instance_double('Articles', slug: 'articles', order_by: '_position asc', label_field_name: :title, localized_fields_names: [:title], fields_by_name: { title: instance_double('Field', name: :title, type: :string) }) } |
| + | # let(:entries) do |
| + | # [ |
| + | # { 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' }, |
| + | # { content_type: type, _position: 1, _label: 'Update #2', title: { fr: 'Mise a jour #2' }, text: { en: 'bla bla', fr: 'blabbla' }, date: '2009/05/12', category: 'General' }, |
| + | # { content_type: type, _position: 2, _label: 'Update #3', title: { fr: 'Mise a jour #2' }, text: { en: 'bla bla', fr: 'blabbla' }, date: '2009/05/12', category: 'General' } |
| + | # ] |
| + | # end |
| + | |
| + | # let(:entry) { nil } |
| + | # subject { repository.next(entry) } |
| + | |
| + | # it { is_expected.to eq nil } |
| + | |
| + | # context 'being last' do |
| + | |
| + | # let(:entry) { instance_double('Entry', content_type: type, _position: 2) } |
| + | # it { repository.send(:collection, type).inspect; is_expected.to eq nil } |
| + | |
| + | # end |
| + | |
| + | # context 'being middle' do |
| + | |
| + | # let(:entry) { instance_double('Entry', content_type: type, _position: 1) } |
| + | # it { expect(subject._position).to eq 2 } |
| + | |
| + | # end |
| + | |
| + | # end |
| + | |
| + | # describe '#previous' do |
| + | |
| + | # let(:type) { instance_double('Articles', slug: 'articles', order_by: '_position asc', label_field_name: :title, localized_fields_names: [:title], fields_by_name: { title: instance_double('Field', name: :title, type: :string) }) } |
| + | # let(:entries) do |
| + | # [ |
| + | # { 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' }, |
| + | # { content_type: type, _position: 1, _label: 'Update #2', title: { fr: 'Mise a jour #2' }, text: { en: 'bla bla', fr: 'blabbla' }, date: '2009/05/12', category: 'General' }, |
| + | # { content_type: type, _position: 2, _label: 'Update #3', title: { fr: 'Mise a jour #2' }, text: { en: 'bla bla', fr: 'blabbla' }, date: '2009/05/12', category: 'General' } |
| + | # ] |
| + | # end |
| + | |
| + | # let(:entry) { nil } |
| + | # subject { repository.previous(entry) } |
| + | |
| + | # it { is_expected.to eq nil } |
| + | |
| + | # context 'being first' do |
| + | |
| + | # let(:entry) { instance_double('Entry', content_type: type, _position: 0) } |
| + | # it { repository.send(:collection, type).inspect; is_expected.to eq nil } |
| + | |
| + | # end |
| + | |
| + | # context 'being middle' do |
| + | |
| + | # let(:entry) { instance_double('Entry', content_type: type, _position: 1) } |
| + | # it { expect(subject._position).to eq 0 } |
| + | |
| + | # end |
| + | |
| + | # end |
| + | |
| + | # describe '#group_by_select_option' do |
| + | |
| + | # let(:type) { nil } |
| + | # let(:name) { nil } |
| + | |
| + | # subject { repository.group_by_select_option(type, name) } |
| + | |
| + | # it { is_expected.to eq({}) } |
| + | |
| + | # context 'select field' do |
| + | |
| + | # let(:fields) do |
| + | # { |
| + | # title: instance_double('TitleField', name: :title, type: :string), |
| + | # category: instance_double('SelectField', name: :category, type: :select, select_options: { en: ['cooking', 'bread'], fr: ['cuisine', 'pain'] }) |
| + | # } |
| + | # end |
| + | # let(:type) { instance_double('Articles', slug: 'articles', order_by: '_position asc', label_field_name: :title, localized_fields_names: [:title, :category], fields_by_name: fields) } |
| + | # let(:name) { :category } |
| + | |
| + | # let(:entries) do |
| + | # [ |
| + | # { content_type: type, _position: 0, _label: 'Recipe #1', category: 'cooking' }, |
| + | # { content_type: type, _position: 1, _label: 'Recipe #2', category: 'bread' }, |
| + | # { content_type: type, _position: 2, _label: 'Recipe #3', category: 'bread' }, |
| + | # { content_type: type, _position: 3, _label: 'Recipe #4', category: 'unknown' } |
| + | # ] |
| + | # end |
| + | |
| + | # before { allow(content_type_repository).to receive(:select_options).and_return(%w(cooking wine bread)) } |
| + | |
| + | # it { expect(subject.size).to eq 4 } |
| + | # it { expect(subject.map { |h| h[:name] }).to eq ['cooking', 'wine', 'bread', nil] } |
| + | # it { expect(subject.map { |h| h[:entries].size }).to eq [1, 0, 2, 1] } |
| + | |
| + | # end |
| + | |
| + | # end |
| + | |
| + | end |
spec/unit/repositories/filesystem/content_type_spec.rb
+0
-130
| @@ | @@ -1,130 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::ContentType do |
| - | |
| - | # let(:fields) { [{ title: { hint: 'Title of the article', type: 'string' } }, { author: { type: 'string', label: 'Fullname of the author' } }] } |
| - | # let(:loader) { instance_double('Loader', list_of_attributes: [{ slug: 'articles', name: 'Articles', fields: fields }]) } |
| - | # let(:site) { instance_double('Site', default_locale: :en, locales: [:en, :fr]) } |
| - | # let(:locale) { :en } |
| - | |
| - | # let(:repository) { Locomotive::Steam::Repositories::Filesystem::ContentType.new(loader, site, locale) } |
| - | |
| - | # describe '#collection' do |
| - | |
| - | # subject { repository.send(:collection).first } |
| - | |
| - | # it { expect(subject.class).to eq Locomotive::Steam::Repositories::Filesystem::Models::ContentType } |
| - | |
| - | # it 'applies the sanitizer' do |
| - | # expect(subject.name).to eq('Articles') |
| - | # expect(subject.slug).to eq('articles') |
| - | # expect(subject.fields.size).to eq 2 |
| - | # expect(subject.fields_by_name.size).to eq 2 |
| - | # end |
| - | |
| - | # describe 'a field of the first element' do |
| - | |
| - | # subject { repository.send(:collection).first.fields.first } |
| - | |
| - | # it { expect(subject.class).to eq Locomotive::Steam::Repositories::Filesystem::Models::ContentTypeField } |
| - | |
| - | # it 'has properties' do |
| - | # expect(subject.name).to eq :title |
| - | # expect(subject.label).to eq 'Title' |
| - | # expect(subject.hint).to eq 'Title of the article' |
| - | # expect(subject.type).to eq :string |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # describe '#by_slug' do |
| - | |
| - | # let(:slug) { nil } |
| - | # subject { repository.by_slug(slug) } |
| - | |
| - | # it { is_expected.to eq nil } |
| - | |
| - | # context 'existing content type' do |
| - | |
| - | # let(:slug) { 'articles' } |
| - | # it { expect(subject.name).to eq 'Articles' } |
| - | |
| - | # end |
| - | |
| - | # context 'slug is already a content type' do |
| - | |
| - | # let(:slug) { instance_double('ContentType') } |
| - | # it { is_expected.to eq slug } |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # describe '#fields_for' do |
| - | |
| - | # let(:type) { nil } |
| - | # subject { repository.fields_for(type) } |
| - | |
| - | # it { is_expected.to eq nil } |
| - | |
| - | # context 'with fields' do |
| - | |
| - | # let(:type) { instance_double('ContentType', fields: [true]) } |
| - | # it { is_expected.to eq([true]) } |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # describe '#look_for_unique_fields' do |
| - | |
| - | # let(:type) { nil } |
| - | # subject { repository.look_for_unique_fields(type) } |
| - | |
| - | # it { is_expected.to eq nil } |
| - | |
| - | # context 'with fields' do |
| - | |
| - | # let(:field) { instance_double('Field', name: :title) } |
| - | # let(:type) { instance_double('ContentType', query_fields: [field])} |
| - | |
| - | # it { expect(subject).to eq(title: field) } |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # describe '#select_options' do |
| - | |
| - | # let(:type) { repository.by_slug('articles') } |
| - | # let(:name) { nil } |
| - | # subject { repository.select_options(type, name) } |
| - | |
| - | # it { is_expected.to eq nil } |
| - | |
| - | # context 'a select field' do |
| - | |
| - | # let(:fields) do |
| - | # [ |
| - | # { title: { hint: 'Title of the article', type: 'string' } }, |
| - | # { category: { type: 'select', select_options: { en: ['cooking', 'bread'], fr: ['cuisine', 'pain'] } } } |
| - | # ] |
| - | # end |
| - | |
| - | # let(:name) { :category } |
| - | # it { is_expected.to eq %w(cooking bread) } |
| - | |
| - | # context 'not a select field' do |
| - | |
| - | # let(:name) { :title } |
| - | # it { is_expected.to eq nil } |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
spec/unit/repositories/filesystem/models/content_entry_spec.rb
+0
-184
| @@ | @@ -1,184 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::Models::ContentEntry do |
| - | |
| - | # let(:fields) { {} } |
| - | # let(:type) { instance_double('ContentType', slug: 'articles', label_field_name: :title, localized_fields_names: [:title], fields_by_name: fields) } |
| - | # let(:attributes) { { title: 'Hello world', _slug: 'hello-world' } } |
| - | # let(:content_entry) do |
| - | # Locomotive::Steam::Repositories::Filesystem::Models::ContentEntry.new(attributes).tap do |entry| |
| - | # entry.content_type = type |
| - | # end |
| - | # end |
| - | |
| - | # describe '#valid?' do |
| - | |
| - | # let(:fields) { { title: instance_double('TitleField', name: :title, type: :string, required?: true)} } |
| - | |
| - | # subject { content_entry.valid? } |
| - | # it { is_expected.to eq true } |
| - | |
| - | # context 'missing attribute' do |
| - | |
| - | # let(:attributes) { {} } |
| - | # it { is_expected.to eq false } |
| - | # it { subject; expect(content_entry.errors[:title]).to eq(["can't not be blank"]) } |
| - | # it { subject; expect(content_entry.errors.empty?).to eq false } |
| - | |
| - | # end |
| - | |
| - | # describe 'adding a custom error message' do |
| - | |
| - | # before { content_entry.errors.add(:title, 'is mandatory') } |
| - | |
| - | # it { expect(content_entry.errors[:title]).to eq(['is mandatory']) } |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # describe '#_label' do |
| - | |
| - | # subject { content_entry._label } |
| - | # it { is_expected.to eq 'Hello world' } |
| - | |
| - | # end |
| - | |
| - | # describe '#_id' do |
| - | |
| - | # subject { content_entry._id } |
| - | # it { is_expected.to eq 'hello-world' } |
| - | |
| - | # end |
| - | |
| - | # describe '#content_type_slug' do |
| - | |
| - | # subject { content_entry.content_type_slug } |
| - | # it { is_expected.to eq 'articles' } |
| - | |
| - | # end |
| - | |
| - | # describe '#localized_attributes' do |
| - | |
| - | # subject { content_entry.localized_attributes } |
| - | # it { is_expected.to include :seo_title } |
| - | # it { is_expected.to include :title } |
| - | # it { is_expected.to include :_slug } |
| - | |
| - | # end |
| - | |
| - | # describe 'dynamic attributes' do |
| - | |
| - | # let(:field_type) { :string } |
| - | # let(:attributes) { { my_field: value } } |
| - | # let(:field) { instance_double('Field', name: :my_field, type: field_type) } |
| - | # before { allow(type).to receive(:fields_by_name).and_return(my_field: field) } |
| - | |
| - | # subject { content_entry.my_field } |
| - | |
| - | # describe 'unable to cast it' do |
| - | |
| - | # let(:field_type) { :float } |
| - | # let(:value) { [] } |
| - | # it { is_expected.to eq nil } |
| - | |
| - | # end |
| - | |
| - | # context 'no provided value, should return nil' do |
| - | |
| - | # let(:attributes) { {} } |
| - | # it { is_expected.to eq nil } |
| - | |
| - | # end |
| - | |
| - | # context 'a string' do |
| - | # let(:value) { 'Hello world' } |
| - | # it { is_expected.to eq 'Hello world' } |
| - | # context 'localized' do |
| - | # let(:value) { { en: 'Hello world', fr: 'Bonjour monde' } } |
| - | # it { is_expected.to eq({ en: 'Hello world', fr: 'Bonjour monde' }) } |
| - | # end |
| - | # end |
| - | |
| - | # context 'an integer' do |
| - | # let(:field_type) { :integer } |
| - | # let(:value) { '42' } |
| - | # it { is_expected.to eq 42 } |
| - | # context 'localized' do |
| - | # let(:value) { { en: 42, fr: '42' } } |
| - | # it { is_expected.to eq({ en: 42, fr: 42 }) } |
| - | # end |
| - | # end |
| - | |
| - | # context 'a float' do |
| - | # let(:field_type) { :float } |
| - | # let(:value) { '42.0' } |
| - | # it { is_expected.to eq 42.0 } |
| - | # context 'localized' do |
| - | # let(:value) { { en: 42.0, fr: '42.0' } } |
| - | # it { is_expected.to eq({ en: 42.0, fr: 42.0 }) } |
| - | # end |
| - | # end |
| - | |
| - | # context 'a date' do |
| - | # let(:field_type) { :date } |
| - | # let(:value) { '2007/06/29' } |
| - | # let(:date) { Date.parse('2007/06/29') } |
| - | # it { is_expected.to eq date } |
| - | # context 'localized' do |
| - | # let(:value) { { en: '2007/06/29', fr: date } } |
| - | # it { is_expected.to eq({ en: date, fr: date }) } |
| - | # end |
| - | # end |
| - | |
| - | # context 'a date time' do |
| - | # 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 } |
| - | # context 'localized' do |
| - | # let(:value) { { en: '2007/06/29 00:00:00', fr: datetime } } |
| - | # it { is_expected.to eq({ en: datetime, fr: datetime }) } |
| - | # 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' } |
| - | # it { expect(subject.type).to eq :belongs_to } |
| - | # it { expect(subject.target_slugs).to eq ['john-doe'] } |
| - | # it { expect(subject.source).to eq content_entry } |
| - | # it { expect(subject.field).to eq field } |
| - | # end |
| - | |
| - | # context 'a has_many relationship' do |
| - | # let(:field_type) { :has_many } |
| - | # let(:value) { nil } |
| - | # it { expect(subject.type).to eq :has_many } |
| - | # it { expect(subject.target_slugs).to eq [] } |
| - | # it { expect(subject.source).to eq content_entry } |
| - | # it { expect(subject.field).to eq field } |
| - | # end |
| - | |
| - | # context 'a many_to_many relationship' do |
| - | # let(:field_type) { :many_to_many } |
| - | # let(:value) { ['john-doe', 'jane-doe'] } |
| - | # it { expect(subject.type).to eq :many_to_many } |
| - | # it { expect(subject.target_slugs).to eq ['john-doe', 'jane-doe'] } |
| - | # it { expect(subject.source).to eq content_entry } |
| - | # it { expect(subject.field).to eq field } |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
spec/unit/repositories/filesystem/models/content_type_spec.rb
+0
-50
| @@ | @@ -1,50 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::Models::ContentType do |
| - | |
| - | # let(:fields) { [instance_double('Field1', label: 'Title', name: :title, localized: false), instance_double('Field2', label: 'Author', name: :author, localized: true)] } |
| - | # let(:content_type) do |
| - | # Locomotive::Steam::Repositories::Filesystem::Models::ContentType.new(name: 'Articles').tap do |type| |
| - | # type.fields = fields |
| - | # end |
| - | # end |
| - | |
| - | # describe '#label_field_name' do |
| - | |
| - | # subject { content_type.label_field_name } |
| - | # it { is_expected.to eq :title } |
| - | |
| - | # context 'defined within the content type itself' do |
| - | |
| - | # before { allow(content_type.attributes).to receive(:[]).with(:label_field_name).and_return('author') } |
| - | # it { is_expected.to eq :author } |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # describe '#localized_fields_names' do |
| - | |
| - | # subject { content_type.localized_fields_names } |
| - | # it { is_expected.to eq [:author] } |
| - | |
| - | # 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/yaml_loaders/content_entry_spec.rb
+0
-22
| @@ | @@ -1,22 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::ContentEntry do |
| - | |
| - | # let(:root_path) { default_fixture_site_path } |
| - | # let(:cache) { NoCacheStore.new } |
| - | # let(:content_type) { instance_double('Articles', slug: 'bands') } |
| - | # let(:loader) { Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::ContentEntry.new(root_path, cache) } |
| - | |
| - | # describe '#list_of_attributes' do |
| - | |
| - | # subject { loader.list_of_attributes(content_type).sort { |a, b| a[:_label] <=> b[:_label] } } |
| - | |
| - | # it 'tests various stuff' do |
| - | # expect(subject.size).to eq 3 |
| - | # expect(subject.first[:_label]).to eq 'Alice in Chains' |
| - | # expect(subject.first[:content_type]).to eq content_type |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
spec/unit/repositories/filesystem/yaml_loaders/content_type_spec.rb
+0
-20
| @@ | @@ -1,20 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::ContentType do |
| - | |
| - | # let(:root_path) { default_fixture_site_path } |
| - | # let(:cache) { NoCacheStore.new } |
| - | # let(:loader) { Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::ContentType.new(root_path, cache) } |
| - | |
| - | # describe '#list_of_attributes' do |
| - | |
| - | # subject { loader.list_of_attributes.sort { |a, b| a[:slug] <=> b[:slug] } } |
| - | |
| - | # it 'tests various stuff' do |
| - | # expect(subject.size).to eq 5 |
| - | # expect(subject.first[:slug]).to eq('bands') |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
spec/unit/repositories/filesystem/yaml_loaders/page_spec.rb
+0
-21
| @@ | @@ -1,21 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::Page do |
| - | |
| - | # let(:root_path) { default_fixture_site_path } |
| - | # let(:default_locale) { :en } |
| - | # let(:cache) { NoCacheStore.new } |
| - | # let(:loader) { Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::Page.new(root_path, default_locale, cache) } |
| - | |
| - | # describe '#list_of_attributes' do |
| - | |
| - | # subject { loader.list_of_attributes.sort { |a, b| a[:_fullpath] <=> b[:_fullpath] } } |
| - | |
| - | # it 'tests various stuff' do |
| - | # expect(subject.size).to eq 21 |
| - | # expect(subject.first[:title]).to eq({ en: 'Page not found' }) |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
spec/unit/repositories/filesystem/yaml_loaders/site_spec.rb
+0
-17
| @@ | @@ -1,17 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::Site do |
| - | |
| - | # let(:root_path) { default_fixture_site_path } |
| - | # let(:cache) { NoCacheStore.new } |
| - | # let(:loader) { Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::Site.new(root_path, cache) } |
| - | |
| - | # describe '#attributes' do |
| - | |
| - | # subject { loader.attributes } |
| - | |
| - | # it { expect(subject[:name]).to eq 'Sample website' } |
| - | |
| - | # end |
| - | |
| - | # end |
spec/unit/repositories/filesystem/yaml_loaders/snippet_spec.rb
+0
-23
| @@ | @@ -1,23 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::Snippet do |
| - | |
| - | # let(:root_path) { default_fixture_site_path } |
| - | # let(:default_locale) { :en } |
| - | # let(:cache) { NoCacheStore.new } |
| - | # let(:loader) { Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::Snippet.new(root_path, default_locale, cache) } |
| - | |
| - | # describe '#list_of_attributes' do |
| - | |
| - | # subject { loader.list_of_attributes.sort { |a, b| a[:name] <=> b[:name] } } |
| - | |
| - | # it 'tests various stuff' do |
| - | # expect(subject.size).to eq 4 |
| - | # expect(subject.first[:slug]).to eq('a_complicated-one') |
| - | # expect(subject[1][:name]).to eq('Footer') |
| - | # expect(subject[1][:slug]).to eq('footer') |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
spec/unit/repositories/filesystem/yaml_loaders/translation_spec.rb
+0
-21
| @@ | @@ -1,21 +0,0 @@ |
| - | # require 'spec_helper' |
| - | |
| - | # describe Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::Translation do |
| - | |
| - | # let(:root_path) { default_fixture_site_path } |
| - | # let(:cache) { NoCacheStore.new } |
| - | # let(:loader) { Locomotive::Steam::Repositories::Filesystem::YAMLLoaders::Translation.new(root_path, cache) } |
| - | |
| - | # describe '#list_of_attributes' do |
| - | |
| - | # subject { loader.list_of_attributes } |
| - | |
| - | # it 'tests various stuff' do |
| - | # expect(subject.size).to eq 1 |
| - | # expect(subject.first[:key]).to eq('powered_by') |
| - | # expect(subject.first[:values]).to eq({ 'en' => 'Powered by', 'fr' => 'Propulsé par' }) |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |