dataset manipulations in memory has got now its own adapter + new association and default_attribute methods for the mapper (which enables embedded collections) + refactor the page yaml_loader and sanitizer accordingly
did
committed Feb 22, 2015
commit 8edbfcc37236601743e683d4e2bbf91f85aa2e44
Showing 35
changed files with
1250 additions
and 966 deletions
locomotive/steam/adapters/filesystem.rb b/lib/locomotive/steam/adapters/filesystem.rb
+35
-14
| @@ | @@ -1,7 +1,4 @@ |
| - | require_relative 'filesystem/dataset' |
| - | require_relative 'filesystem/order' |
| - | require_relative 'filesystem/condition' |
| - | require_relative 'filesystem/query' |
| + | require_relative 'memory' |
| require_relative 'filesystem/simple_cache_store' | |
| @@ | @@ -9,6 +6,10 @@ require_relative 'filesystem/yaml_loader' |
| require_relative 'filesystem/yaml_loaders/site' | |
| require_relative 'filesystem/yaml_loaders/page' | |
| + | require_relative 'filesystem/sanitizer' |
| + | require_relative 'filesystem/sanitizers/simple' |
| + | require_relative 'filesystem/sanitizers/page' |
| + | |
| module Locomotive::Steam | |
| class FilesystemAdapter < Struct.new(:site_path) | |
| @@ | @@ -23,6 +24,10 @@ module Locomotive::Steam |
| build_yaml_loaders(cache) | |
| end | |
| + | register :sanitizers do |
| + | build_sanitizers |
| + | end |
| + | |
| def initialize(site_path) | |
| super | |
| @datasets = {} | |
| @@ | @@ -43,7 +48,7 @@ module Locomotive::Steam |
| private | |
| def _query(mapper, scope, &block) | |
| - | Locomotive::Steam::Adapters::Filesystem::Query.new(all(mapper, scope), scope.locale, &block) |
| + | Locomotive::Steam::Adapters::Memory::Query.new(all(mapper, scope), scope.locale, &block) |
| end | |
| def memoized_dataset(mapper, scope) | |
| @@ | @@ -52,18 +57,22 @@ module Locomotive::Steam |
| end | |
| def dataset(mapper, scope) | |
| - | Locomotive::Steam::Adapters::Filesystem::Dataset.new(mapper.name).tap do |dataset| |
| + | Locomotive::Steam::Adapters::Memory::Dataset.new(mapper.name).tap do |dataset| |
| @datasets[mapper.name] = dataset | |
| + | populate_dataset(dataset, mapper, scope) |
| + | end |
| + | end |
| + | def populate_dataset(dataset, mapper, scope) |
| + | sanitizers[mapper.name].with(scope) do |sanitizer| |
| collection(mapper, scope).each do |attributes| | |
| entity = mapper.to_entity(attributes) | |
| - | |
| - | # assign the site_id to the entity + sanitize attributes |
| - | # specific to the Filesystem adapter |
| - | entity[:site_id] = scope.site.id if scope.site |
| - | |
| dataset.insert(entity) | |
| + | |
| + | sanitizer.apply_to(entity) |
| end | |
| + | |
| + | sanitizer.apply_to(dataset) |
| end | |
| end | |
| @@ | @@ -73,12 +82,24 @@ module Locomotive::Steam |
| def build_yaml_loaders(cache) | |
| %i(site page).inject({}) do |memo, name| | |
| - | _name = name.to_s.singularize.camelize |
| - | klass = "Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::#{_name}".constantize |
| - | memo[name] = klass.new(site_path, cache) |
| + | memo[name] = build_klass('YAMLLoaders', name).new(site_path, cache) |
| + | memo |
| end | |
| end | |
| + | def build_sanitizers |
| + | hash = Hash.new { build_klass('Sanitizers', :simple).new } |
| + | %i(pages).inject(hash) do |memo, name| |
| + | memo[name] = build_klass('Sanitizers', name).new |
| + | memo |
| + | end |
| + | end |
| + | |
| + | def build_klass(type, name) |
| + | _name = name.to_s.singularize.camelize |
| + | "Locomotive::Steam::Adapters::Filesystem::#{type}::#{_name}".constantize |
| + | end |
| + | |
| end | |
| end | |
locomotive/steam/adapters/filesystem/concerns/queryable.rb b/lib/locomotive/steam/adapters/filesystem/concerns/queryable.rb
+0
-65
| @@ | @@ -1,65 +0,0 @@ |
| - | # module Locomotive |
| - | # module Steam |
| - | # module Adapters |
| - | # module Filesystem |
| - | # module Concerns |
| - | |
| - | # module Queryable |
| - | |
| - | # extend ActiveSupport::Concern |
| - | |
| - | # def query(*args, &block) |
| - | # _locale = respond_to?(:current_locale) ? current_locale : nil |
| - | # Filesystem::Query.new(memoized_collection(*args), _locale, &block) |
| - | # end |
| - | |
| - | # private |
| - | |
| - | # def localized_attribute(object, method) |
| - | # if (values = object.send(method)).is_a?(Hash) |
| - | # values[current_locale] |
| - | # else |
| - | # values |
| - | # end |
| - | # end |
| - | |
| - | # def memoized_collection(*args) |
| - | # return @collection if @collection |
| - | |
| - | # @collection = collection(*args) |
| - | # end |
| - | |
| - | # def collection(*args) |
| - | # _collection = loader.list_of_attributes(*args).map do |attributes| |
| - | # collection_options[:model].new(attributes) |
| - | # end |
| - | |
| - | # sanitize!(_collection) |
| - | # end |
| - | |
| - | # def sanitize!(collection) |
| - | # sanitizer.try(:apply_to, collection) || collection |
| - | # end |
| - | |
| - | # def sanitizer |
| - | # return unless (klass = collection_options[:sanitizer]) |
| - | # klass.new(site.default_locale, site.locales) |
| - | # end |
| - | |
| - | # module ClassMethods |
| - | |
| - | # def set_collection(options = {}) |
| - | # class_eval do |
| - | # define_method(:collection_options) { options } |
| - | # end |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
| - | # end |
| - | # end |
| - | # end |
| - | # end |
locomotive/steam/adapters/filesystem/condition.rb b/lib/locomotive/steam/adapters/filesystem/condition.rb
+0
-103
| @@ | @@ -1,103 +0,0 @@ |
| - | module Locomotive::Steam |
| - | module Adapters |
| - | module Filesystem |
| - | |
| - | class Condition |
| - | |
| - | class UnsupportedOperator < StandardError; end |
| - | |
| - | OPERATORS = %i(== eq ne neq matches gt gte lt lte size all in nin).freeze |
| - | |
| - | attr_reader :field, :operator, :value |
| - | |
| - | def initialize(operator_and_field, value, locale) |
| - | @locale = locale.try(:to_sym) |
| - | @operator_and_field, @value = operator_and_field, value |
| - | @operator, @field = :==, operator_and_field |
| - | |
| - | decode_operator_and_field! |
| - | end |
| - | |
| - | def matches?(entry) |
| - | entry_value = entry_value(entry) |
| - | |
| - | adapt_operator!(entry_value) |
| - | case @operator |
| - | when :== then entry_value == @value |
| - | when :eq then entry_value == @value |
| - | when :ne then entry_value != @value |
| - | when :neq then entry_value != @value |
| - | when :matches then @value =~ entry_value |
| - | when :gt then entry_value > @value |
| - | when :gte then entry_value >= @value |
| - | when :lt then entry_value < @value |
| - | when :lte then entry_value <= @value |
| - | when :size then entry_value.size == @value |
| - | when :all then array_contains?([*@value], entry_value) |
| - | when :in, :nin then value_is_in_entry_value?(entry_value) |
| - | else |
| - | raise UnknownConditionInScope.new("#{@operator} is unknown or not implemented.") |
| - | end |
| - | end |
| - | |
| - | def to_s |
| - | "#{field} #{operator} #{@value.to_s}" |
| - | end |
| - | |
| - | protected |
| - | |
| - | def entry_value(entry) |
| - | value = entry.send(@field) |
| - | |
| - | if value.respond_to?(:translations) |
| - | value[@locale] |
| - | else |
| - | value |
| - | end |
| - | end |
| - | |
| - | def decode_operator_and_field! |
| - | if match = @operator_and_field.match(/^(?<field>[a-z0-9_-]+)\.(?<operator>.*)$/) |
| - | @field = match[:field].to_sym |
| - | @operator = match[:operator].to_sym |
| - | check_operator! |
| - | end |
| - | |
| - | @operator = :matches if @value.is_a?(Regexp) |
| - | end |
| - | |
| - | def adapt_operator!(value) |
| - | case value |
| - | when Array |
| - | @operator = :in if @operator == :== |
| - | end |
| - | end |
| - | |
| - | def value_is_in_entry_value?(value) |
| - | _matches = if value.is_a?(Array) |
| - | array_contains?([*value], [*@value]) |
| - | else |
| - | [*@value].include?(value) |
| - | end |
| - | @operator == :in ? _matches : !_matches |
| - | end |
| - | |
| - | private |
| - | |
| - | def check_operator! |
| - | raise UnsupportedOperator.new unless OPERATORS.include?(@operator) |
| - | end |
| - | |
| - | def array_contains?(source, target) |
| - | if target.size == 0 |
| - | source.size == 0 |
| - | else |
| - | source & target == target |
| - | end |
| - | end |
| - | |
| - | end |
| - | |
| - | end |
| - | end |
| - | end |
locomotive/steam/adapters/filesystem/dataset.rb b/lib/locomotive/steam/adapters/filesystem/dataset.rb
+0
-75
| @@ | @@ -1,75 +0,0 @@ |
| - | module Locomotive::Steam |
| - | module Adapters |
| - | module Filesystem |
| - | |
| - | class Dataset |
| - | |
| - | class PrimaryKey |
| - | def initialize |
| - | @current = 0 |
| - | end |
| - | |
| - | def increment! |
| - | yield(@current += 1) |
| - | @current |
| - | end |
| - | end |
| - | |
| - | attr_reader :records, :name |
| - | |
| - | def initialize(name) |
| - | @name = name |
| - | clear! |
| - | end |
| - | |
| - | def insert(record) |
| - | @primary_key.increment! do |id| |
| - | record[identity] = id |
| - | records[id] = record |
| - | end |
| - | end |
| - | |
| - | def update(record) |
| - | records[record[identity]] = records[record[identity]].deep_merge(record) |
| - | end |
| - | |
| - | def delete(id) |
| - | records.delete(id) |
| - | end |
| - | |
| - | def size |
| - | records.size |
| - | end |
| - | |
| - | def all |
| - | records.values |
| - | end |
| - | |
| - | def find(id) |
| - | records.fetch(id) do |
| - | raise Locomotive::Steam::Repository::RecordNotFound, "could not find #{name} with #{identity} = #{id}" |
| - | end |
| - | end |
| - | |
| - | def exists?(id) |
| - | !!id && records.has_key?(id) |
| - | end |
| - | |
| - | # def query |
| - | # Query.new(self) |
| - | # end |
| - | |
| - | def clear! |
| - | @records = {} |
| - | @primary_key = PrimaryKey.new |
| - | end |
| - | |
| - | private |
| - | |
| - | def identity |
| - | @identity ||= :_id |
| - | end |
| - | end |
| - | end |
| - | end |
| - | end |
locomotive/steam/adapters/filesystem/order.rb b/lib/locomotive/steam/adapters/filesystem/order.rb
+0
-59
| @@ | @@ -1,59 +0,0 @@ |
| - | module Locomotive::Steam |
| - | module Adapters |
| - | module Filesystem |
| - | |
| - | class Order |
| - | |
| - | attr_reader :list |
| - | |
| - | def initialize(*args) |
| - | strings = args.compact |
| - | |
| - | @list = (case args.size |
| - | when 0 then [] |
| - | when 1 then args.first.split(',').collect { |s| build(s.strip) } |
| - | else |
| - | args.collect { |s| build(s) } |
| - | end) |
| - | end |
| - | |
| - | def empty? |
| - | @list.empty? |
| - | end |
| - | |
| - | def apply_to(entry, locale) |
| - | @list.collect do |(name, direction)| |
| - | value = entry.send(name) |
| - | asc?(direction) ? Asc.new(value) : Desc.new(value) |
| - | end |
| - | end |
| - | |
| - | def asc?(direction) |
| - | direction.nil? || direction == :asc |
| - | end |
| - | |
| - | private |
| - | |
| - | def build(string) |
| - | pattern = string.include?('.') ? '.' : ' ' |
| - | string.downcase.split(pattern).map(&:to_sym) |
| - | end |
| - | |
| - | class Direction |
| - | attr_reader :obj |
| - | def initialize(obj); @obj = obj; end |
| - | end |
| - | |
| - | class Asc < Direction |
| - | def <=>(other); @obj <=> other.obj; end |
| - | end |
| - | |
| - | class Desc < Direction |
| - | def <=>(other); other.obj <=> @obj; end |
| - | end |
| - | |
| - | end |
| - | |
| - | end |
| - | end |
| - | end |
locomotive/steam/adapters/filesystem/query.rb b/lib/locomotive/steam/adapters/filesystem/query.rb
+0
-100
| @@ | @@ -1,100 +0,0 @@ |
| - | require 'forwardable' |
| - | |
| - | module Locomotive::Steam |
| - | module Adapters |
| - | module Filesystem |
| - | |
| - | class Query |
| - | |
| - | include Enumerable |
| - | extend Forwardable |
| - | |
| - | def_delegators :all, :each, :to_s, :to_a, :empty?, :size |
| - | |
| - | alias :length :size |
| - | alias :count :size |
| - | |
| - | attr_reader :conditions |
| - | |
| - | def initialize(dataset, locale = nil, &block) |
| - | @dataset = dataset |
| - | @conditions = [] |
| - | @sorting = nil |
| - | @limit = nil |
| - | @offset = 0 |
| - | @locale = locale |
| - | instance_eval(&block) if block_given? |
| - | end |
| - | |
| - | def where(conditions = {}) |
| - | @conditions += conditions.map { |name, value| Condition.new(name, value, @locale) } |
| - | self |
| - | end |
| - | |
| - | def +(query) |
| - | @conditions += query.conditions |
| - | self |
| - | end |
| - | |
| - | def order_by(*args) |
| - | @sorting = Order.new(*args) |
| - | end |
| - | |
| - | def limit(num) |
| - | @limit = num |
| - | self |
| - | end |
| - | |
| - | def offset(num) |
| - | @offset = num |
| - | self |
| - | end |
| - | |
| - | def ==(other) |
| - | if other.kind_of? Array |
| - | all == other |
| - | else |
| - | super |
| - | end |
| - | end |
| - | |
| - | def all |
| - | limited sorted(filtered) |
| - | end |
| - | |
| - | def sorted(entries) |
| - | return entries if @sorting.empty? |
| - | |
| - | entries.sort_by { |entry| @sorting.apply_to(entry, @locale) } |
| - | end |
| - | |
| - | def limited(entries) |
| - | return [] if @limit == 0 |
| - | return entries if @offset == 0 && @limit.nil? |
| - | |
| - | subentries = entries.drop(@offset || 0) |
| - | if @limit.kind_of? Integer |
| - | subentries.take(@limit) |
| - | else |
| - | subentries |
| - | end |
| - | end |
| - | |
| - | def filtered |
| - | @dataset.all.dup.find_all do |entry| |
| - | accepted = true |
| - | |
| - | @conditions.each do |_condition| |
| - | unless _condition.matches?(entry) |
| - | accepted = false |
| - | break # no to go further |
| - | end |
| - | end |
| - | accepted |
| - | end |
| - | end # filtered |
| - | |
| - | end |
| - | end |
| - | end |
| - | end |
locomotive/steam/adapters/filesystem/sanitizer.rb b/lib/locomotive/steam/adapters/filesystem/sanitizer.rb
+45
-0
| @@ | @@ -0,0 +1,45 @@ |
| + | module Locomotive::Steam |
| + | module Adapters |
| + | module Filesystem |
| + | |
| + | module Sanitizer |
| + | |
| + | extend Forwardable |
| + | |
| + | def_delegators :@scope, :site, :locale, :locales, :default_locale |
| + | |
| + | attr_reader :scope |
| + | |
| + | def setup(scope) |
| + | @scope = scope |
| + | self |
| + | end |
| + | |
| + | def with(scope, &block) |
| + | setup(scope) |
| + | yield(self) |
| + | end |
| + | |
| + | def apply_to(entity_or_dataset) |
| + | if entity_or_dataset.respond_to?(:all) |
| + | apply_to_dataset(entity_or_dataset) |
| + | else |
| + | apply_to_entity(entity_or_dataset) |
| + | end |
| + | end |
| + | |
| + | def apply_to_dataset(dataset) |
| + | dataset |
| + | end |
| + | |
| + | def apply_to_entity(entity) |
| + | entity |
| + | end |
| + | |
| + | alias :current_locale :locale |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/adapters/filesystem/sanitizers/page.rb b/lib/locomotive/steam/adapters/filesystem/sanitizers/page.rb
+155
-145
| @@ | @@ -1,145 +1,155 @@ |
| - | # module Locomotive |
| - | # module Steam |
| - | # module Repositories |
| - | # module Filesystem |
| - | # module Sanitizers |
| - | |
| - | # class Page < Struct.new(:default_locale, :locales) |
| - | |
| - | # def initialize(default_locale, locales) |
| - | # super |
| - | # @content_types = {} |
| - | # @localized = {} |
| - | # locales.each { |locale| @localized[locale] = {} } |
| - | # end |
| - | |
| - | # def apply_to(collection) |
| - | # sorted_collection(collection).each do |page| |
| - | # locales.each do |locale| |
| - | # set_fullpath_for(page, locale) |
| - | # modify_if_templatized(page, locale) |
| - | # build_editable_elements(page, locale) |
| - | # use_default_locale_template_path(page, locale) |
| - | # set_default_redirect_type(page, locale) |
| - | # end |
| - | # end |
| - | # end |
| - | |
| - | # # If the page does not have a template in a locale |
| - | # # then use the template of the default locale |
| - | # # |
| - | # def use_default_locale_template_path(page, locale) |
| - | # paths = page.template_path |
| - | |
| - | # if paths[locale] == false |
| - | # paths[locale] = paths[default_locale] |
| - | # end |
| - | # end |
| - | |
| - | # def set_default_redirect_type(page, locale) |
| - | # if page.redirect_url[locale] |
| - | # page.attributes[:redirect_type] ||= 301 |
| - | # end |
| - | # end |
| - | |
| - | # def build_editable_elements(page, locale) |
| - | # elements = page.editable_elements[locale] || {} |
| - | # elements.stringify_keys! |
| - | |
| - | # elements.each do |name, content| |
| - | # segments = name.split('/') |
| - | # block, slug = segments[0..-2].join('/'), segments.last |
| - | # block = nil if block.blank? |
| - | |
| - | # elements[name] = Filesystem::Models::EditableElement.new(block, slug, content) |
| - | # end |
| - | # end |
| - | |
| - | # def modify_if_templatized(page, locale) |
| - | # if page.templatized? |
| - | # # change the slug of a templatized page |
| - | # page[:slug][locale] = 'content-type-template' |
| - | |
| - | # # this also means to change the fullpath |
| - | # if page[:fullpath][locale] |
| - | # page[:fullpath][locale].gsub!(/[^\/]+$/, 'content-type-template') |
| - | # end |
| - | |
| - | # # make sure its children will have its content type |
| - | # set_content_type(page._fullpath, page.content_type) |
| - | # elsif content_type = fetch_content_type(parent_fullpath(page)) |
| - | # # not a templatized page but it becomes one because |
| - | # # its parent is one of them |
| - | # page[:content_type] = content_type |
| - | # end |
| - | # end |
| - | |
| - | # def set_fullpath_for(page, locale) |
| - | # page._fullpath ||= page.attributes.delete(:_fullpath) |
| - | |
| - | # slug = fullpath = page.slug[locale].try(:dasherize) |
| - | |
| - | # return if slug.blank? |
| - | |
| - | # if page.depth > 1 |
| - | # base = parent_fullpath(page) |
| - | # fullpath = (fetch_localized_fullpath(base, locale) || base) + '/' + slug |
| - | # end |
| - | |
| - | # set_localized_fullpath(page._fullpath, fullpath, locale) |
| - | # page[:fullpath][locale] = fullpath |
| - | # end |
| - | |
| - | # def depth(page) |
| - | # return page.depth if page.depth |
| - | |
| - | # page.depth = page[:_fullpath].split('/').size |
| - | |
| - | # slug = get_slug(page) |
| - | |
| - | # if page.depth == 1 && %w(index 404).include?(slug) |
| - | # page.depth = 0 |
| - | # end |
| - | |
| - | # page.depth |
| - | # end |
| - | |
| - | # def get_slug(page) |
| - | # if page.slug.is_a?(Hash) |
| - | # page.slug.values.compact.first |
| - | # else |
| - | # page.slug |
| - | # end |
| - | # end |
| - | |
| - | # def sorted_collection(collection) |
| - | # collection.sort_by { |page| depth(page) } |
| - | # end |
| - | |
| - | # def parent_fullpath(page) |
| - | # page._fullpath.split('/')[0..-2].join('/') |
| - | # end |
| - | |
| - | # def fetch_content_type(fullpath) |
| - | # @content_types[fullpath] |
| - | # end |
| - | |
| - | # def set_content_type(fullpath, value) |
| - | # @content_types[fullpath] = value |
| - | # end |
| - | |
| - | # def fetch_localized_fullpath(fullpath, locale) |
| - | # @localized[locale][fullpath] |
| - | # end |
| - | |
| - | # def set_localized_fullpath(fullpath, value, locale) |
| - | # @localized[locale][fullpath] = value |
| - | # end |
| - | |
| - | # end |
| - | |
| - | # end |
| - | # end |
| - | # end |
| - | # end |
| - | # end |
| + | module Locomotive::Steam |
| + | module Adapters |
| + | module Filesystem |
| + | module Sanitizers |
| + | |
| + | class Page # < Struct.new(:default_locale, :locales) |
| + | |
| + | include Adapters::Filesystem::Sanitizer |
| + | |
| + | # def initialize(default_locale, locales) |
| + | # super |
| + | # @content_types = {} |
| + | # @localized = {} |
| + | # locales.each { |locale| @localized[locale] = {} } |
| + | # end |
| + | |
| + | def setup(scope) |
| + | super.tap do |
| + | @ids = {} |
| + | @content_types = {} |
| + | @localized = Hash.new { {} } |
| + | end |
| + | end |
| + | |
| + | def apply_to_entity(entity) |
| + | entity[:site_id] = scope.site.id if scope.site |
| + | |
| + | # required to get the parent_id |
| + | @ids[entity._fullpath] = entity._id |
| + | |
| + | locales.each do |locale| |
| + | set_default_redirect_type(entity, locale) |
| + | end |
| + | end |
| + | |
| + | def apply_to_dataset(dataset) |
| + | sorted_collection(dataset.records).each do |page| |
| + | locales.each do |locale| |
| + | set_parent_id(page) |
| + | set_fullpath_for(page, locale) |
| + | modify_if_templatized(page, locale) |
| + | use_default_locale_template_path(page, locale) |
| + | end |
| + | end |
| + | end |
| + | |
| + | # when this is called, the @ids hash has been populated completely |
| + | def set_parent_id(page) |
| + | page.parent_id = @ids[parent_fullpath(page)] |
| + | end |
| + | |
| + | # If the page does not have a template in a locale |
| + | # then use the template of the default locale |
| + | # |
| + | def use_default_locale_template_path(page, locale) |
| + | paths = page.template_path |
| + | |
| + | if paths[locale] == false |
| + | paths[locale] = paths[default_locale] |
| + | end |
| + | end |
| + | |
| + | def set_default_redirect_type(page, locale) |
| + | if page.redirect_url[locale] |
| + | page.attributes[:redirect_type] ||= 301 |
| + | end |
| + | end |
| + | |
| + | def modify_if_templatized(page, locale) |
| + | if page.templatized? |
| + | # change the slug of a templatized page |
| + | page[:slug][locale] = 'content-type-template' |
| + | |
| + | # this also means to change the fullpath |
| + | if page[:fullpath][locale] |
| + | page[:fullpath][locale].gsub!(/[^\/]+$/, 'content-type-template') |
| + | end |
| + | |
| + | # make sure its children will have its content type |
| + | set_content_type(page._fullpath, page.content_type) |
| + | elsif content_type = fetch_content_type(parent_fullpath(page)) |
| + | # not a templatized page but it becomes one because |
| + | # its parent is one of them |
| + | page[:content_type] = content_type |
| + | end |
| + | end |
| + | |
| + | def set_fullpath_for(page, locale) |
| + | page._fullpath ||= page.attributes.delete(:_fullpath) |
| + | |
| + | slug = fullpath = page.slug[locale].try(:dasherize) |
| + | |
| + | return if slug.blank? |
| + | |
| + | if page.depth > 1 |
| + | base = parent_fullpath(page) |
| + | fullpath = (fetch_localized_fullpath(base, locale) || base) + '/' + slug |
| + | end |
| + | |
| + | set_localized_fullpath(page._fullpath, fullpath, locale) |
| + | page[:fullpath][locale] = fullpath |
| + | end |
| + | |
| + | def depth(page) |
| + | return page.depth if page.depth |
| + | |
| + | page.depth = page[:_fullpath].split('/').size |
| + | |
| + | slug = get_slug(page) |
| + | |
| + | if page.depth == 1 && %w(index 404).include?(slug) |
| + | page.depth = 0 |
| + | end |
| + | |
| + | page.depth |
| + | end |
| + | |
| + | def get_slug(page) |
| + | if page.slug.is_a?(Hash) |
| + | page.slug.values.compact.first |
| + | else |
| + | page.slug |
| + | end |
| + | end |
| + | |
| + | def sorted_collection(collection) |
| + | collection.sort_by { |page| depth(page) } |
| + | end |
| + | |
| + | def parent_fullpath(page) |
| + | page._fullpath.split('/')[0..-2].join('/') |
| + | end |
| + | |
| + | def fetch_content_type(fullpath) |
| + | @content_types[fullpath] |
| + | end |
| + | |
| + | def set_content_type(fullpath, value) |
| + | @content_types[fullpath] = value |
| + | end |
| + | |
| + | def fetch_localized_fullpath(fullpath, locale) |
| + | @localized[locale][fullpath] |
| + | end |
| + | |
| + | def set_localized_fullpath(fullpath, value, locale) |
| + | @localized[locale][fullpath] = value |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
| + | end |
locomotive/steam/adapters/filesystem/sanitizers/simple.rb b/lib/locomotive/steam/adapters/filesystem/sanitizers/simple.rb
+15
-0
| @@ | @@ -0,0 +1,15 @@ |
| + | module Locomotive::Steam |
| + | module Adapters |
| + | module Filesystem |
| + | module Sanitizers |
| + | |
| + | class Simple |
| + | |
| + | include Adapters::Filesystem::Sanitizer |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
| + | end |
locomotive/steam/adapters/filesystem/yaml_loaders/page.rb b/lib/locomotive/steam/adapters/filesystem/yaml_loaders/page.rb
+37
-2
| @@ | @@ -41,9 +41,9 @@ module Locomotive |
| { | |
| title: { locale => attributes.delete(:title) || (default_locale == locale ? slug.humanize : nil) }, | |
| slug: { locale => attributes.delete(:slug) || slug }, | |
| - | editable_elements: { locale => attributes.delete(:editable_elements) }, |
| template_path: { locale => template_path(filepath, attributes, locale) }, | |
| redirect_url: { locale => attributes.delete(:redirect_url) }, | |
| + | editable_elements: build_editable_elements(attributes.delete(:editable_elements), locale), |
| _fullpath: fullpath | |
| }.merge(attributes) | |
| end | |
| @@ | @@ -54,10 +54,11 @@ module Locomotive |
| leaf[:title][locale] = attributes.delete(:title) || slug.humanize | |
| leaf[:slug][locale] = attributes.delete(:slug) || slug | |
| - | leaf[:editable_elements][locale] = attributes.delete(:editable_elements) |
| leaf[:template_path][locale] = template_path(filepath, attributes, locale) | |
| leaf[:redirect_url][locale] = attributes.delete(:redirect_url) | |
| + | update_editable_elements(leaf, attributes.delete(:editable_elements), locale) |
| + | |
| leaf.merge!(attributes) | |
| end | |
| @@ | @@ -100,6 +101,40 @@ module Locomotive |
| filepath.gsub(path, '').gsub(/^\//, '') | |
| end | |
| + | def build_editable_elements(list, locale) |
| + | return [] if list.blank? |
| + | |
| + | list.map do |name, content| |
| + | build_editable_element(name, content, locale) |
| + | end |
| + | end |
| + | |
| + | def update_editable_elements(leaf, list, locale) |
| + | return if list.blank? |
| + | |
| + | list.each do |name, content| |
| + | if el = find_editable_element(leaf, name) |
| + | el[:content][locale] = content |
| + | else |
| + | leaf[:editable_elements] << build_editable_element(name, content, locale) |
| + | end |
| + | end |
| + | end |
| + | |
| + | def find_editable_element(leaf, name) |
| + | leaf[:editable_elements].find do |el| |
| + | [el[:block], el[:slug]].join('/') == name |
| + | end |
| + | end |
| + | |
| + | def build_editable_element(name, content, locale) |
| + | segments = name.to_s.split('/') |
| + | block, slug = segments[0..-2].join('/'), segments.last |
| + | block = nil if block.blank? |
| + | |
| + | { block: block, slug: slug, content: { locale => content } } |
| + | end |
| + | |
| end | |
| end | |
locomotive/steam/adapters/memory.rb b/lib/locomotive/steam/adapters/memory.rb
+42
-0
| @@ | @@ -0,0 +1,42 @@ |
| + | require_relative 'memory/order' |
| + | require_relative 'memory/condition' |
| + | require_relative 'memory/query' |
| + | require_relative 'memory/dataset' |
| + | |
| + | module Locomotive::Steam |
| + | |
| + | class MemoryAdapter < Struct.new(:collection) |
| + | |
| + | def all(mapper, scope) |
| + | memoized_dataset(mapper, scope) |
| + | end |
| + | |
| + | def query(mapper, scope, &block) |
| + | _query(mapper, scope, &block) |
| + | end |
| + | |
| + | private |
| + | |
| + | def _query(mapper, scope, &block) |
| + | Locomotive::Steam::Adapters::Memory::Query.new(all(mapper, scope), scope.locale, &block) |
| + | end |
| + | |
| + | def memoized_dataset(mapper, scope) |
| + | return @dataset if @dataset |
| + | dataset(mapper, scope) |
| + | end |
| + | |
| + | def dataset(mapper, scope) |
| + | Locomotive::Steam::Adapters::Memory::Dataset.new(mapper.name).tap do |dataset| |
| + | collection.each do |attributes| |
| + | entity = mapper.to_entity(attributes) |
| + | dataset.insert(entity) |
| + | end |
| + | end |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | |
locomotive/steam/adapters/memory/condition.rb b/lib/locomotive/steam/adapters/memory/condition.rb
+103
-0
| @@ | @@ -0,0 +1,103 @@ |
| + | module Locomotive::Steam |
| + | module Adapters |
| + | module Memory |
| + | |
| + | class Condition |
| + | |
| + | class UnsupportedOperator < StandardError; end |
| + | |
| + | OPERATORS = %i(== eq ne neq matches gt gte lt lte size all in nin).freeze |
| + | |
| + | attr_reader :field, :operator, :value |
| + | |
| + | def initialize(operator_and_field, value, locale) |
| + | @locale = locale.try(:to_sym) |
| + | @operator_and_field, @value = operator_and_field, value |
| + | @operator, @field = :==, operator_and_field |
| + | |
| + | decode_operator_and_field! |
| + | end |
| + | |
| + | def matches?(entry) |
| + | entry_value = entry_value(entry) |
| + | |
| + | adapt_operator!(entry_value) |
| + | case @operator |
| + | when :== then entry_value == @value |
| + | when :eq then entry_value == @value |
| + | when :ne then entry_value != @value |
| + | when :neq then entry_value != @value |
| + | when :matches then @value =~ entry_value |
| + | when :gt then entry_value > @value |
| + | when :gte then entry_value >= @value |
| + | when :lt then entry_value < @value |
| + | when :lte then entry_value <= @value |
| + | when :size then entry_value.size == @value |
| + | when :all then array_contains?([*@value], entry_value) |
| + | when :in, :nin then value_is_in_entry_value?(entry_value) |
| + | else |
| + | raise UnknownConditionInScope.new("#{@operator} is unknown or not implemented.") |
| + | end |
| + | end |
| + | |
| + | def to_s |
| + | "#{field} #{operator} #{@value.to_s}" |
| + | end |
| + | |
| + | protected |
| + | |
| + | def entry_value(entry) |
| + | value = entry.send(@field) |
| + | |
| + | if value.respond_to?(:translations) |
| + | value[@locale] |
| + | else |
| + | value |
| + | end |
| + | end |
| + | |
| + | def decode_operator_and_field! |
| + | if match = @operator_and_field.match(/^(?<field>[a-z0-9_-]+)\.(?<operator>.*)$/) |
| + | @field = match[:field].to_sym |
| + | @operator = match[:operator].to_sym |
| + | check_operator! |
| + | end |
| + | |
| + | @operator = :matches if @value.is_a?(Regexp) |
| + | end |
| + | |
| + | def adapt_operator!(value) |
| + | case value |
| + | when Array |
| + | @operator = :in if @operator == :== |
| + | end |
| + | end |
| + | |
| + | def value_is_in_entry_value?(value) |
| + | _matches = if value.is_a?(Array) |
| + | array_contains?([*value], [*@value]) |
| + | else |
| + | [*@value].include?(value) |
| + | end |
| + | @operator == :in ? _matches : !_matches |
| + | end |
| + | |
| + | private |
| + | |
| + | def check_operator! |
| + | raise UnsupportedOperator.new unless OPERATORS.include?(@operator) |
| + | end |
| + | |
| + | def array_contains?(source, target) |
| + | if target.size == 0 |
| + | source.size == 0 |
| + | else |
| + | source & target == target |
| + | end |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/adapters/memory/dataset.rb b/lib/locomotive/steam/adapters/memory/dataset.rb
+75
-0
| @@ | @@ -0,0 +1,75 @@ |
| + | module Locomotive::Steam |
| + | module Adapters |
| + | module Memory |
| + | |
| + | class Dataset |
| + | |
| + | class PrimaryKey |
| + | def initialize |
| + | @current = 0 |
| + | end |
| + | |
| + | def increment! |
| + | yield(@current += 1) |
| + | @current |
| + | end |
| + | end |
| + | |
| + | attr_reader :records, :name |
| + | |
| + | def initialize(name) |
| + | @name = name |
| + | clear! |
| + | end |
| + | |
| + | def insert(record) |
| + | @primary_key.increment! do |id| |
| + | record[identity] = id |
| + | records[id] = record |
| + | end |
| + | end |
| + | |
| + | def update(record) |
| + | records[record[identity]] = records[record[identity]].deep_merge(record) |
| + | end |
| + | |
| + | def delete(id) |
| + | records.delete(id) |
| + | end |
| + | |
| + | def size |
| + | records.size |
| + | end |
| + | |
| + | def all |
| + | records.values |
| + | end |
| + | |
| + | def find(id) |
| + | records.fetch(id) do |
| + | raise Locomotive::Steam::Repository::RecordNotFound, "could not find #{name} with #{identity} = #{id}" |
| + | end |
| + | end |
| + | |
| + | def exists?(id) |
| + | !!id && records.has_key?(id) |
| + | end |
| + | |
| + | # def query |
| + | # Query.new(self) |
| + | # end |
| + | |
| + | def clear! |
| + | @records = {} |
| + | @primary_key = PrimaryKey.new |
| + | end |
| + | |
| + | private |
| + | |
| + | def identity |
| + | @identity ||= :_id |
| + | end |
| + | end |
| + | end |
| + | end |
| + | end |
locomotive/steam/adapters/memory/order.rb b/lib/locomotive/steam/adapters/memory/order.rb
+59
-0
| @@ | @@ -0,0 +1,59 @@ |
| + | module Locomotive::Steam |
| + | module Adapters |
| + | module Memory |
| + | |
| + | class Order |
| + | |
| + | attr_reader :list |
| + | |
| + | def initialize(*args) |
| + | strings = args.compact |
| + | |
| + | @list = (case args.size |
| + | when 0 then [] |
| + | when 1 then args.first.split(',').collect { |s| build(s.strip) } |
| + | else |
| + | args.collect { |s| build(s) } |
| + | end) |
| + | end |
| + | |
| + | def empty? |
| + | @list.empty? |
| + | end |
| + | |
| + | def apply_to(entry, locale) |
| + | @list.collect do |(name, direction)| |
| + | value = entry.send(name) |
| + | asc?(direction) ? Asc.new(value) : Desc.new(value) |
| + | end |
| + | end |
| + | |
| + | def asc?(direction) |
| + | direction.nil? || direction == :asc |
| + | end |
| + | |
| + | private |
| + | |
| + | def build(string) |
| + | pattern = string.include?('.') ? '.' : ' ' |
| + | string.downcase.split(pattern).map(&:to_sym) |
| + | end |
| + | |
| + | class Direction |
| + | attr_reader :obj |
| + | def initialize(obj); @obj = obj; end |
| + | end |
| + | |
| + | class Asc < Direction |
| + | def <=>(other); @obj <=> other.obj; end |
| + | end |
| + | |
| + | class Desc < Direction |
| + | def <=>(other); other.obj <=> @obj; end |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
| + | end |
locomotive/steam/adapters/memory/query.rb b/lib/locomotive/steam/adapters/memory/query.rb
+100
-0
| @@ | @@ -0,0 +1,100 @@ |
| + | require 'forwardable' |
| + | |
| + | module Locomotive::Steam |
| + | module Adapters |
| + | module Memory |
| + | |
| + | class Query |
| + | |
| + | include Enumerable |
| + | extend Forwardable |
| + | |
| + | def_delegators :all, :each, :to_s, :to_a, :empty?, :size |
| + | |
| + | alias :length :size |
| + | alias :count :size |
| + | |
| + | attr_reader :conditions |
| + | |
| + | def initialize(dataset, locale = nil, &block) |
| + | @dataset = dataset |
| + | @conditions = [] |
| + | @sorting = nil |
| + | @limit = nil |
| + | @offset = 0 |
| + | @locale = locale |
| + | instance_eval(&block) if block_given? |
| + | end |
| + | |
| + | def where(conditions = {}) |
| + | @conditions += conditions.map { |name, value| Condition.new(name, value, @locale) } |
| + | self |
| + | end |
| + | |
| + | def +(query) |
| + | @conditions += query.conditions |
| + | self |
| + | end |
| + | |
| + | def order_by(*args) |
| + | @sorting = Order.new(*args) |
| + | end |
| + | |
| + | def limit(num) |
| + | @limit = num |
| + | self |
| + | end |
| + | |
| + | def offset(num) |
| + | @offset = num |
| + | self |
| + | end |
| + | |
| + | def ==(other) |
| + | if other.kind_of? Array |
| + | all == other |
| + | else |
| + | super |
| + | end |
| + | end |
| + | |
| + | def all |
| + | limited sorted(filtered) |
| + | end |
| + | |
| + | def sorted(entries) |
| + | return entries if @sorting.blank? |
| + | |
| + | entries.sort_by { |entry| @sorting.apply_to(entry, @locale) } |
| + | end |
| + | |
| + | def limited(entries) |
| + | return [] if @limit == 0 |
| + | return entries if @offset == 0 && @limit.nil? |
| + | |
| + | subentries = entries.drop(@offset || 0) |
| + | if @limit.kind_of? Integer |
| + | subentries.take(@limit) |
| + | else |
| + | subentries |
| + | end |
| + | end |
| + | |
| + | def filtered |
| + | @dataset.all.dup.find_all do |entry| |
| + | accepted = true |
| + | |
| + | @conditions.each do |_condition| |
| + | unless _condition.matches?(entry) |
| + | accepted = false |
| + | break # no to go further |
| + | end |
| + | end |
| + | accepted |
| + | end |
| + | end # filtered |
| + | |
| + | end |
| + | end |
| + | end |
| + | end |
locomotive/steam/entities/editable_element.rb b/lib/locomotive/steam/entities/editable_element.rb
+13
-0
| @@ | @@ -0,0 +1,13 @@ |
| + | module Locomotive::Steam |
| + | |
| + | class EditableElement |
| + | |
| + | include Locomotive::Steam::Models::Entity |
| + | |
| + | attr_accessor :_parent |
| + | |
| + | # TODO |
| + | |
| + | end |
| + | |
| + | end |
locomotive/steam/models.rb b/lib/locomotive/steam/models.rb
+1
-0
| @@ | @@ -1,5 +1,6 @@ |
| require_relative 'models/concerns/validation' | |
| require_relative 'models/i18n_field' | |
| + | require_relative 'models/association' |
| require_relative 'models/entity' | |
| require_relative 'models/mapper' | |
| require_relative 'models/scope' | |
locomotive/steam/models/association.rb b/lib/locomotive/steam/models/association.rb
+29
-0
| @@ | @@ -0,0 +1,29 @@ |
| + | require 'locomotive/steam/adapters/memory' |
| + | require 'morphine' |
| + | |
| + | module Locomotive::Steam |
| + | module Models |
| + | |
| + | # Note: represents an embedded collection |
| + | class Association < SimpleDelegator |
| + | |
| + | include Morphine |
| + | |
| + | register :adapter do |
| + | Locomotive::Steam::MemoryAdapter.new(nil) |
| + | end |
| + | |
| + | def initialize(repository_klass, collection) |
| + | adapter.collection = collection |
| + | @repository = repository_klass.new(adapter) |
| + | super(@repository) |
| + | end |
| + | |
| + | def attach(name, entity) |
| + | @repository.send(:"#{name}=", entity) |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
locomotive/steam/models/mapper.rb b/lib/locomotive/steam/models/mapper.rb
+55
-8
| @@ | @@ -3,27 +3,43 @@ module Locomotive::Steam |
| class Mapper | |
| - | attr_reader :name, :options, :localized_attributes |
| + | attr_reader :name, :options, :default_attributes, :localized_attributes, :associations |
| + | |
| + | def initialize(name, options, repository, &block) |
| + | @name, @options, @repository = name, options, repository |
| - | def initialize(name, options, &block) |
| - | @name, @options = name, options |
| @localized_attributes = [] | |
| + | @default_attributes = [] |
| + | @associations = [] |
| instance_eval(&block) if block_given? | |
| end | |
| - | def set_localized_attributes(*args) |
| + | def localized_attributes(*args) |
| @localized_attributes += [*args] | |
| end | |
| + | def default_attribute(name, value) |
| + | @default_attributes += [[name.to_sym, value]] |
| + | end |
| + | |
| + | # Note: only works for embedded-type associations |
| + | def association(name, repository_klass) |
| + | @associations += [[name.to_sym, repository_klass]] |
| + | end |
| + | |
| def to_entity(attributes) | |
| - | entity_klass.new(serialize(attributes)) |
| + | entity_klass.new(serialize(attributes)).tap do |entity| |
| + | attach_entity_to_associations(entity) |
| + | set_default_attributes(entity) |
| + | end |
| end | |
| def serialize(attributes) | |
| - | localized_attributes.each do |name| |
| - | attributes[name] = I18nField.new(name, attributes[name]) |
| - | end |
| + | serialize_localized_attributes(attributes) |
| + | |
| + | serialize_associations(attributes) |
| + | |
| attributes | |
| end | |
| @@ | @@ -31,6 +47,37 @@ module Locomotive::Steam |
| options[:entity] | |
| end | |
| + | private |
| + | |
| + | # create a proxy class for each localized attribute |
| + | def serialize_localized_attributes(attributes) |
| + | @localized_attributes.each do |name| |
| + | attributes[name] = I18nField.new(name, attributes[name]) |
| + | end |
| + | end |
| + | |
| + | # build the embedded associations |
| + | def serialize_associations(attributes) |
| + | @associations.each do |name, repository_klass| |
| + | attributes[name] = Association.new(repository_klass, attributes[name]) |
| + | end |
| + | end |
| + | |
| + | def attach_entity_to_associations(entity) |
| + | @associations.each do |(name, _)| |
| + | key = name.to_s.singularize.to_sym |
| + | entity[name].attach(key, entity) |
| + | end |
| + | end |
| + | |
| + | 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 |
| + | end |
| + | |
| end | |
| end | |
locomotive/steam/models/repository.rb b/lib/locomotive/steam/models/repository.rb
+1
-1
| @@ | @@ -43,7 +43,7 @@ module Locomotive::Steam |
| def mapper | |
| name, options, block = mapper_options | |
| - | @mapper ||= Mapper.new(name, options, &block) |
| + | @mapper ||= Mapper.new(name, options, self, &block) |
| end | |
| def scope | |
locomotive/steam/repositories/editable_element_repository.rb b/lib/locomotive/steam/repositories/editable_element_repository.rb
+21
-0
| @@ | @@ -0,0 +1,21 @@ |
| + | module Locomotive |
| + | module Steam |
| + | |
| + | class EditableElementRepository |
| + | |
| + | include Models::Repository |
| + | |
| + | attr_accessor :page |
| + | |
| + | mapping :editable_elements, entity: EditableElement do |
| + | localized_attributes :content, :source, :default_content, :default_source_url |
| + | |
| + | default_attribute :page, -> (repository) { repository.page } |
| + | end |
| + | |
| + | # TODO |
| + | |
| + | end |
| + | |
| + | end |
| + | end |
locomotive/steam/repositories/page_repository.rb b/lib/locomotive/steam/repositories/page_repository.rb
+14
-7
| @@ | @@ -5,8 +5,12 @@ module Locomotive |
| include Models::Repository | |
| + | # Entity mapping |
| mapping :pages, entity: Page do | |
| - | set_localized_attributes :title, :slug, :permalink, :editable_elements, :template, :template_path, :redirect_url, :fullpath, :seo_title, :meta_description, :meta_keywords |
| + | localized_attributes :title, :slug, :permalink, :editable_elements, :template, :template_path, :redirect_url, :fullpath, :seo_title, :meta_description, :meta_keywords |
| + | |
| + | # embedded association |
| + | association :editable_elements, EditableElementRepository |
| end | |
| # Engine: site.pages.ordered_pages(conditions) [WIP] | |
| @@ | @@ -17,20 +21,22 @@ module Locomotive |
| end.all | |
| end | |
| - | # Engine: site.pages.where(handle: handle).first |
| + | # Engine: site.pages.where(handle: handle).first [TODO] |
| def by_handle(handle) | |
| query { where(handle: handle) }.first | |
| end | |
| + | # [TODO] |
| def by_fullpath(path) | |
| query { where(fullpath: path) }.first | |
| end | |
| + | # [TODO] |
| def matching_fullpath(list) | |
| all('fullpath.in' => list) | |
| end | |
| - | # Engine: ??? |
| + | # Engine: ??? [TODO] |
| def template_for(entry, handle = nil) | |
| conditions = { templatized?: true, content_type: entry.try(:content_type_slug) } | |
| @@ | @@ -41,11 +47,12 @@ module Locomotive |
| end | |
| end | |
| + | # [TODO] |
| def root | |
| query { where(fullpath: 'index') }.first | |
| end | |
| - | # Engine: page.parent |
| + | # Engine: page.parent [TODO] |
| def parent_of(page) | |
| return nil if page.nil? || page.index? | |
| @@ | @@ -57,7 +64,7 @@ module Locomotive |
| by_fullpath(path) | |
| end | |
| - | # Engine: page.ancestors_and_self |
| + | # Engine: page.ancestors_and_self [TODO] |
| def ancestors_of(page) | |
| return [] if page.nil? | |
| @@ | @@ -69,7 +76,7 @@ module Locomotive |
| all('fullpath.in' => ['index'] + paths) | |
| end | |
| - | # Engine: page.children |
| + | # Engine: page.children [TODO] |
| def children_of(page) | |
| return [] if page.nil? | |
| @@ | @@ -82,7 +89,7 @@ module Locomotive |
| all(conditions) | |
| end | |
| - | # Engine: page.editable_elements |
| + | # Engine: page.editable_elements [TODO] |
| def editable_elements_of(page) | |
| return nil if page.nil? | |
| localized_attribute(page, :editable_elements).values | |
locomotive/steam/repositories/site_repository.rb b/lib/locomotive/steam/repositories/site_repository.rb
+1
-1
| @@ | @@ -6,7 +6,7 @@ module Locomotive |
| include Models::Repository | |
| mapping :sites, entity: Site do | |
| - | set_localized_attributes :seo_title, :meta_description, :meta_keywords |
| + | localized_attributes :seo_title, :meta_description, :meta_keywords |
| end | |
| def by_handle_or_domain(handle, domain) | |
spec/unit/adapters/filesystem/condition_spec.rb
+0
-124
| @@ | @@ -1,124 +0,0 @@ |
| - | require 'spec_helper' |
| - | |
| - | require_relative '../../../../lib/locomotive/steam/adapters/filesystem/condition.rb' |
| - | |
| - | describe Locomotive::Steam::Adapters::Filesystem::Condition do |
| - | |
| - | let(:entry) { instance_double('Site', { title: { en: 'Awesome Site' }, content: 'foo' }) } |
| - | let(:locale) { :en } |
| - | let(:field) { :title } |
| - | let(:operator) { :eq } |
| - | let(:name) { "#{field}.#{operator}"} |
| - | let(:value) { 'Awesome Site' } |
| - | |
| - | subject { Locomotive::Steam::Adapters::Filesystem::Condition.new(name, value, locale) } |
| - | |
| - | describe '#entry_value' do |
| - | context 'i18n' do |
| - | let(:name) { 'title.eq' } |
| - | let(:value) { 'Awesome Site' } |
| - | |
| - | context 'single entry' do |
| - | specify('should be match') do |
| - | expect(subject.matches?(entry)).to eq true |
| - | end |
| - | |
| - | specify('return value') do |
| - | expect(subject.send(:entry_value, entry)).to eq(value) |
| - | end |
| - | end |
| - | end |
| - | context 'regular way' do |
| - | let(:name) { 'content.eq' } |
| - | let(:value) { 'foo' } |
| - | |
| - | context 'single entry' do |
| - | specify('should be match') do |
| - | expect(subject.matches?(entry)).to eq true |
| - | end |
| - | |
| - | specify('return value') do |
| - | expect(subject.send(:entry_value, entry)).to eq(value) |
| - | end |
| - | end |
| - | end |
| - | end |
| - | |
| - | describe '#decode_operator_and_field!' do |
| - | before { subject.send(:decode_operator_and_field!) } |
| - | |
| - | context 'with normal value' do |
| - | specify('name should be left part of dot') { expect(subject.field).to eq(field) } |
| - | specify('operator should be right part of dot') { expect(subject.operator).to eq(operator) } |
| - | specify('right_operand should be value') { expect(subject.value).to eq(value) } |
| - | end |
| - | |
| - | context 'with regex value' do |
| - | let(:value) { /^[a-z]$/ } |
| - | specify('operator should be matchtes') { expect(subject.operator).to eq(:matches) } |
| - | end |
| - | end |
| - | |
| - | describe '#decode_operator_and_field!' do |
| - | context 'with unsupported operator' do |
| - | let(:name) { 'domains.unsupported' } |
| - | specify('should be throw Exception') do |
| - | expect do |
| - | subject.send(:decode_operator_and_field!) |
| - | end.to raise_error Locomotive::Steam::Adapters::Filesystem::Condition::UnsupportedOperator |
| - | end |
| - | end |
| - | end |
| - | |
| - | describe '#adapt_operator!' do |
| - | let(:name) { 'domains.==' } |
| - | before do |
| - | subject.send(:decode_operator_and_field!) |
| - | subject.send(:adapt_operator!, value) |
| - | end |
| - | context 'with single value' do |
| - | let(:value) { 'sample.example.com' } |
| - | specify('operator should be :==') { expect(subject.operator).to eq(:==) } |
| - | end |
| - | context 'with array of values' do |
| - | let(:value) { ['sample.example.com'] } |
| - | specify('operator should be :in') { expect(subject.operator).to eq(:in) } |
| - | end |
| - | end |
| - | |
| - | describe '#array_contains?' do |
| - | let(:source) { [1, 2, 3, 4] } |
| - | let(:target) { [1, 2, 3] } |
| - | context 'with target contains in source' do |
| - | specify('should be true') do |
| - | expect(subject.send(:array_contains?, source, target)).to eq true |
| - | end |
| - | end |
| - | end |
| - | |
| - | describe '#value_in_right_operand?' do |
| - | context 'value contains in right operand' do |
| - | let(:value) { [1, 2, 3, 4] } |
| - | let(:right_operand) { [1, 2, 3] } |
| - | |
| - | before do |
| - | allow(subject).to receive(:operator).and_return(operator) |
| - | allow(subject).to receive(:right_operand).and_return(right_operand) |
| - | end |
| - | |
| - | context 'with operator :in' do |
| - | let(:operator) { :in } |
| - | specify('should return true') do |
| - | expect(subject.send(:value_is_in_entry_value?, value)).to eq true |
| - | end |
| - | end |
| - | |
| - | context 'with other operator' do |
| - | let(:operator) { :nin } |
| - | specify('should not return true') do |
| - | expect(subject.send(:value_is_in_entry_value?, value)).to eq false |
| - | end |
| - | end |
| - | end |
| - | end |
| - | end |
spec/unit/adapters/filesystem/dataset_spec.rb
+0
-73
| @@ | @@ -1,73 +0,0 @@ |
| - | require 'spec_helper' |
| - | |
| - | require_relative '../../../../lib/locomotive/steam/adapters/filesystem/dataset.rb' |
| - | |
| - | describe Locomotive::Steam::Adapters::Filesystem::Dataset do |
| - | |
| - | let(:john) do |
| - | { |
| - | firstname: 'John', |
| - | lastname: 'Doe', |
| - | email: 'john@example.com', |
| - | age: 24 |
| - | } |
| - | end |
| - | |
| - | let(:jane) do |
| - | { |
| - | firstname: 'Jane', |
| - | lastname: 'Doe', |
| - | email: 'jane@example.com', |
| - | age: 20 |
| - | } |
| - | end |
| - | |
| - | let(:alex) do |
| - | { |
| - | firstname: 'Alex', |
| - | lastname: 'Turam', |
| - | email: 'alex@example.com', |
| - | age: 26 |
| - | } |
| - | end |
| - | |
| - | subject { Locomotive::Steam::Adapters::Filesystem::Dataset.new(:foo) } #(loader) } |
| - | |
| - | before do |
| - | [john.to_hash, jane.to_hash, alex.to_hash].each do |record| |
| - | subject.insert record |
| - | end |
| - | end |
| - | |
| - | describe '#all' do |
| - | it { expect(subject.all).to eq [john.to_hash, jane.to_hash, alex.to_hash] } |
| - | end |
| - | |
| - | describe '#find' do |
| - | specify do |
| - | expect(subject.find(john[:_id])).to eq(john.to_hash) |
| - | end |
| - | end |
| - | |
| - | describe '#update' do |
| - | before do |
| - | subject.update(jane.to_hash.merge(lastname: 'birkin')) |
| - | end |
| - | |
| - | specify do |
| - | expect(subject.find(jane[:_id]).fetch(:lastname)).to eq('birkin') |
| - | end |
| - | end |
| - | |
| - | describe '#exists?' do |
| - | let(:dataset) { Locomotive::Steam::Adapters::Filesystem::Dataset.new(:dummy) } |
| - | before do |
| - | dataset.instance_variable_set('@records', { 1 => 'Record 1', 2 => 'Record 2' }) |
| - | end |
| - | |
| - | it { expect(dataset.exists?(2)).to eq true } |
| - | it { expect(dataset.exists?(3)).to eq false } |
| - | it { expect(dataset.exists?(nil)).to eq false } |
| - | |
| - | end |
| - | end |
spec/unit/adapters/filesystem/order_spec.rb
+0
-67
| @@ | @@ -1,67 +0,0 @@ |
| - | require 'spec_helper' |
| - | |
| - | require_relative '../../../../lib/locomotive/steam/adapters/filesystem/order.rb' |
| - | |
| - | describe Locomotive::Steam::Adapters::Filesystem::Order do |
| - | |
| - | let(:order) { Locomotive::Steam::Adapters::Filesystem::Order.new(*input) } |
| - | |
| - | describe '#list' do |
| - | |
| - | subject { order.list } |
| - | |
| - | let(:input) { nil } |
| - | it { is_expected.to eq [] } |
| - | |
| - | context 'a string' do |
| - | |
| - | let(:input) { 'name' } |
| - | it { is_expected.to eq [[:name]] } |
| - | |
| - | end |
| - | |
| - | context 'two strings' do |
| - | |
| - | let(:input) { ['name', 'date.desc'] } |
| - | it { is_expected.to eq [[:name], [:date, :desc]] } |
| - | |
| - | end |
| - | |
| - | context 'a string with a comma' do |
| - | |
| - | let(:input) { 'name, date desc' } |
| - | it { is_expected.to eq [[:name], [:date, :desc]] } |
| - | |
| - | end |
| - | |
| - | end |
| - | |
| - | describe '#apply_to' do |
| - | |
| - | subject { order.apply_to(entry, :en) } |
| - | |
| - | let(:input) { 'title, date desc' } |
| - | let(:entry) { instance_double('Entry', title: 'foo', date: Time.now) } |
| - | it { expect(subject.map(&:class)).to eq([Locomotive::Steam::Adapters::Filesystem::Order::Asc, Locomotive::Steam::Adapters::Filesystem::Order::Desc]) } |
| - | |
| - | end |
| - | |
| - | describe 'sort' do |
| - | |
| - | let(:array) { |
| - | [ |
| - | instance_double('Entry1', id: 1, title: 'b', position: 1), |
| - | instance_double('Entry2', id: 2, title: 'b', position: 2), |
| - | instance_double('Entry3', id: 3, title: 'a', position: 3), |
| - | instance_double('Entry3', id: 4, title: 'c', position: 1) |
| - | ] |
| - | } |
| - | let(:input) { 'title, position desc' } |
| - | |
| - | subject { array.sort_by { |entry| order.apply_to(entry, :en) } } |
| - | |
| - | it { expect(subject.map(&:id)).to eq([3, 2, 1, 4]) } |
| - | |
| - | end |
| - | |
| - | end |
spec/unit/adapters/filesystem/query_spec.rb
+0
-64
| @@ | @@ -1,64 +0,0 @@ |
| - | require 'spec_helper' |
| - | |
| - | require_relative '../../../../lib/locomotive/steam/adapters/filesystem/dataset.rb' |
| - | require_relative '../../../../lib/locomotive/steam/adapters/filesystem/condition.rb' |
| - | require_relative '../../../../lib/locomotive/steam/adapters/filesystem/query.rb' |
| - | |
| - | describe Locomotive::Steam::Adapters::Filesystem::Query do |
| - | |
| - | let(:entry_1) { OpenStruct.new(name: 'foo', id: 1) } |
| - | let(:entry_2) { OpenStruct.new(name: 'bar', id: 2) } |
| - | let(:entry_3) { OpenStruct.new(name: 'zone', id: 3) } |
| - | let(:records) { { 1 => entry_1, 2 => entry_2, 3 => entry_3 } } |
| - | let(:dataset) { Locomotive::Steam::Adapters::Filesystem::Dataset.new(:test) } |
| - | let(:locale) { :en } |
| - | |
| - | let(:query) { Locomotive::Steam::Adapters::Filesystem::Query } |
| - | |
| - | before { allow(dataset).to receive(:records).and_return(records) } |
| - | |
| - | describe '#limited' do |
| - | specify do |
| - | expect( |
| - | query.new(dataset, locale) do |
| - | limit(1) |
| - | end.all |
| - | ).to eq([entry_1]) |
| - | end |
| - | end |
| - | |
| - | describe '#order_by' do |
| - | |
| - | context 'asc' do |
| - | specify do |
| - | expect( |
| - | query.new(dataset, locale) do |
| - | order_by('name asc') |
| - | end.all.map(&:name) |
| - | ).to eq(['bar', 'foo', 'zone']) |
| - | end |
| - | end |
| - | |
| - | context 'desc' do |
| - | specify do |
| - | expect( |
| - | query.new(dataset, locale) do |
| - | order_by('name desc') |
| - | end.all.map(&:name) |
| - | ).to eq(['zone', 'foo', 'bar']) |
| - | end |
| - | end |
| - | end |
| - | |
| - | describe '#where' do |
| - | specify do |
| - | expect( |
| - | query.new(dataset, locale) do |
| - | where('name.eq' => 'foo'). |
| - | where('id.lt' => 2) |
| - | end.all.map(&:name) |
| - | ).to eq(['foo']) |
| - | end |
| - | end |
| - | |
| - | end |
spec/unit/adapters/filesystem_adapter_spec.rb
+45
-0
| @@ | @@ -0,0 +1,45 @@ |
| + | require 'spec_helper' |
| + | |
| + | 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(:adapter) { Locomotive::Steam::FilesystemAdapter.new(nil) } |
| + | |
| + | describe '#query' do |
| + | |
| + | let(:collection) { [OpenStruct.new(site_id: 42, name: 'Hello world')] } |
| + | |
| + | before do |
| + | allow(mapper).to receive(:to_entity) { |arg| arg } |
| + | allow(adapter).to receive(:collection).and_return(collection) |
| + | end |
| + | |
| + | subject { adapter.query(mapper, scope) { where(name: 'Hello world') } } |
| + | |
| + | context 'not scoped by a site' do |
| + | |
| + | let(:site) { nil } |
| + | it { expect(subject.first.name).to eq 'Hello world' } |
| + | |
| + | end |
| + | |
| + | context 'scoped by a site' do |
| + | |
| + | let(:site) { instance_double('Site', id: 42) } |
| + | it { expect(subject.first.name).to eq 'Hello world' } |
| + | |
| + | context 'unknown site id' do |
| + | |
| + | let(:site) { instance_double('Site', id: 1) } |
| + | it { expect(subject.first).to eq nil } |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | end |
spec/unit/adapters/filesystem_spec.rb
+0
-45
| @@ | @@ -1,45 +0,0 @@ |
| - | require 'spec_helper' |
| - | |
| - | 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(:adapter) { Locomotive::Steam::FilesystemAdapter.new(nil) } |
| - | |
| - | describe '#query' do |
| - | |
| - | let(:collection) { [OpenStruct.new(site_id: 42, name: 'Hello world')] } |
| - | |
| - | before do |
| - | allow(mapper).to receive(:to_entity) { |arg| arg } |
| - | allow(adapter).to receive(:collection).and_return(collection) |
| - | end |
| - | |
| - | subject { adapter.query(mapper, scope) { where(name: 'Hello world') } } |
| - | |
| - | context 'not scoped by a site' do |
| - | |
| - | let(:site) { nil } |
| - | it { expect(subject.first.name).to eq 'Hello world' } |
| - | |
| - | end |
| - | |
| - | context 'scoped by a site' do |
| - | |
| - | let(:site) { instance_double('Site', id: 42) } |
| - | it { expect(subject.first.name).to eq 'Hello world' } |
| - | |
| - | context 'unknown site id' do |
| - | |
| - | let(:site) { instance_double('Site', id: 1) } |
| - | it { expect(subject.first).to eq nil } |
| - | |
| - | end |
| - | |
| - | end |
| - | |
| - | end |
| - | |
| - | end |
spec/unit/adapters/memory/condition_spec.rb
+125
-0
| @@ | @@ -0,0 +1,125 @@ |
| + | require 'spec_helper' |
| + | |
| + | require_relative '../../../../lib/locomotive/steam/adapters/memory/condition.rb' |
| + | |
| + | describe Locomotive::Steam::Adapters::Memory::Condition do |
| + | |
| + | let(:title) { instance_double('Title', translations: { en: 'Awesome Site' }, :[] => 'Awesome Site') } |
| + | let(:entry) { instance_double('Site', { title: title, content: 'foo' }) } |
| + | let(:locale) { :en } |
| + | let(:field) { :title } |
| + | let(:operator) { :eq } |
| + | let(:name) { "#{field}.#{operator}"} |
| + | let(:value) { 'Awesome Site' } |
| + | |
| + | subject { Locomotive::Steam::Adapters::Memory::Condition.new(name, value, locale) } |
| + | |
| + | describe '#entry_value' do |
| + | context 'i18n' do |
| + | let(:name) { 'title.eq' } |
| + | let(:value) { 'Awesome Site' } |
| + | |
| + | context 'single entry' do |
| + | specify('match') do |
| + | expect(subject.matches?(entry)).to eq true |
| + | end |
| + | |
| + | specify('return value') do |
| + | expect(subject.send(:entry_value, entry)).to eq(value) |
| + | end |
| + | end |
| + | end |
| + | context 'regular way' do |
| + | let(:name) { 'content.eq' } |
| + | let(:value) { 'foo' } |
| + | |
| + | context 'single entry' do |
| + | specify('should be match') do |
| + | expect(subject.matches?(entry)).to eq true |
| + | end |
| + | |
| + | specify('return value') do |
| + | expect(subject.send(:entry_value, entry)).to eq(value) |
| + | end |
| + | end |
| + | end |
| + | end |
| + | |
| + | describe '#decode_operator_and_field!' do |
| + | before { subject.send(:decode_operator_and_field!) } |
| + | |
| + | context 'with normal value' do |
| + | specify('name should be left part of dot') { expect(subject.field).to eq(field) } |
| + | specify('operator should be right part of dot') { expect(subject.operator).to eq(operator) } |
| + | specify('right_operand should be value') { expect(subject.value).to eq(value) } |
| + | end |
| + | |
| + | context 'with regex value' do |
| + | let(:value) { /^[a-z]$/ } |
| + | specify('operator should be matchtes') { expect(subject.operator).to eq(:matches) } |
| + | end |
| + | end |
| + | |
| + | describe '#decode_operator_and_field!' do |
| + | context 'with unsupported operator' do |
| + | let(:name) { 'domains.unsupported' } |
| + | specify('should be throw Exception') do |
| + | expect do |
| + | subject.send(:decode_operator_and_field!) |
| + | end.to raise_error Locomotive::Steam::Adapters::Memory::Condition::UnsupportedOperator |
| + | end |
| + | end |
| + | end |
| + | |
| + | describe '#adapt_operator!' do |
| + | let(:name) { 'domains.==' } |
| + | before do |
| + | subject.send(:decode_operator_and_field!) |
| + | subject.send(:adapt_operator!, value) |
| + | end |
| + | context 'with single value' do |
| + | let(:value) { 'sample.example.com' } |
| + | specify('operator should be :==') { expect(subject.operator).to eq(:==) } |
| + | end |
| + | context 'with array of values' do |
| + | let(:value) { ['sample.example.com'] } |
| + | specify('operator should be :in') { expect(subject.operator).to eq(:in) } |
| + | end |
| + | end |
| + | |
| + | describe '#array_contains?' do |
| + | let(:source) { [1, 2, 3, 4] } |
| + | let(:target) { [1, 2, 3] } |
| + | context 'with target contains in source' do |
| + | specify('should be true') do |
| + | expect(subject.send(:array_contains?, source, target)).to eq true |
| + | end |
| + | end |
| + | end |
| + | |
| + | describe '#value_in_right_operand?' do |
| + | context 'value contains in right operand' do |
| + | let(:value) { [1, 2, 3, 4] } |
| + | let(:right_operand) { [1, 2, 3] } |
| + | |
| + | before do |
| + | allow(subject).to receive(:operator).and_return(operator) |
| + | allow(subject).to receive(:right_operand).and_return(right_operand) |
| + | end |
| + | |
| + | context 'with operator :in' do |
| + | let(:operator) { :in } |
| + | specify('should return true') do |
| + | expect(subject.send(:value_is_in_entry_value?, value)).to eq true |
| + | end |
| + | end |
| + | |
| + | context 'with other operator' do |
| + | let(:operator) { :nin } |
| + | specify('should not return true') do |
| + | expect(subject.send(:value_is_in_entry_value?, value)).to eq false |
| + | end |
| + | end |
| + | end |
| + | end |
| + | end |
spec/unit/adapters/memory/dataset_spec.rb
+73
-0
| @@ | @@ -0,0 +1,73 @@ |
| + | require 'spec_helper' |
| + | |
| + | require_relative '../../../../lib/locomotive/steam/adapters/memory/dataset.rb' |
| + | |
| + | describe Locomotive::Steam::Adapters::Memory::Dataset do |
| + | |
| + | let(:john) do |
| + | { |
| + | firstname: 'John', |
| + | lastname: 'Doe', |
| + | email: 'john@example.com', |
| + | age: 24 |
| + | } |
| + | end |
| + | |
| + | let(:jane) do |
| + | { |
| + | firstname: 'Jane', |
| + | lastname: 'Doe', |
| + | email: 'jane@example.com', |
| + | age: 20 |
| + | } |
| + | end |
| + | |
| + | let(:alex) do |
| + | { |
| + | firstname: 'Alex', |
| + | lastname: 'Turam', |
| + | email: 'alex@example.com', |
| + | age: 26 |
| + | } |
| + | end |
| + | |
| + | subject { Locomotive::Steam::Adapters::Memory::Dataset.new(:foo) } #(loader) } |
| + | |
| + | before do |
| + | [john.to_hash, jane.to_hash, alex.to_hash].each do |record| |
| + | subject.insert record |
| + | end |
| + | end |
| + | |
| + | describe '#all' do |
| + | it { expect(subject.all).to eq [john.to_hash, jane.to_hash, alex.to_hash] } |
| + | end |
| + | |
| + | describe '#find' do |
| + | specify do |
| + | expect(subject.find(john[:_id])).to eq(john.to_hash) |
| + | end |
| + | end |
| + | |
| + | describe '#update' do |
| + | before do |
| + | subject.update(jane.to_hash.merge(lastname: 'birkin')) |
| + | end |
| + | |
| + | specify do |
| + | expect(subject.find(jane[:_id]).fetch(:lastname)).to eq('birkin') |
| + | end |
| + | end |
| + | |
| + | describe '#exists?' do |
| + | let(:dataset) { Locomotive::Steam::Adapters::Memory::Dataset.new(:dummy) } |
| + | before do |
| + | dataset.instance_variable_set('@records', { 1 => 'Record 1', 2 => 'Record 2' }) |
| + | end |
| + | |
| + | it { expect(dataset.exists?(2)).to eq true } |
| + | it { expect(dataset.exists?(3)).to eq false } |
| + | it { expect(dataset.exists?(nil)).to eq false } |
| + | |
| + | end |
| + | end |
spec/unit/adapters/memory/order_spec.rb
+67
-0
| @@ | @@ -0,0 +1,67 @@ |
| + | require 'spec_helper' |
| + | |
| + | require_relative '../../../../lib/locomotive/steam/adapters/memory/order.rb' |
| + | |
| + | describe Locomotive::Steam::Adapters::Memory::Order do |
| + | |
| + | let(:order) { Locomotive::Steam::Adapters::Memory::Order.new(*input) } |
| + | |
| + | describe '#list' do |
| + | |
| + | subject { order.list } |
| + | |
| + | let(:input) { nil } |
| + | it { is_expected.to eq [] } |
| + | |
| + | context 'a string' do |
| + | |
| + | let(:input) { 'name' } |
| + | it { is_expected.to eq [[:name]] } |
| + | |
| + | end |
| + | |
| + | context 'two strings' do |
| + | |
| + | let(:input) { ['name', 'date.desc'] } |
| + | it { is_expected.to eq [[:name], [:date, :desc]] } |
| + | |
| + | end |
| + | |
| + | context 'a string with a comma' do |
| + | |
| + | let(:input) { 'name, date desc' } |
| + | it { is_expected.to eq [[:name], [:date, :desc]] } |
| + | |
| + | end |
| + | |
| + | end |
| + | |
| + | describe '#apply_to' do |
| + | |
| + | subject { order.apply_to(entry, :en) } |
| + | |
| + | let(:input) { 'title, date desc' } |
| + | let(:entry) { instance_double('Entry', title: 'foo', date: Time.now) } |
| + | it { expect(subject.map(&:class)).to eq([Locomotive::Steam::Adapters::Memory::Order::Asc, Locomotive::Steam::Adapters::Memory::Order::Desc]) } |
| + | |
| + | end |
| + | |
| + | describe 'sort' do |
| + | |
| + | let(:array) { |
| + | [ |
| + | instance_double('Entry1', id: 1, title: 'b', position: 1), |
| + | instance_double('Entry2', id: 2, title: 'b', position: 2), |
| + | instance_double('Entry3', id: 3, title: 'a', position: 3), |
| + | instance_double('Entry3', id: 4, title: 'c', position: 1) |
| + | ] |
| + | } |
| + | let(:input) { 'title, position desc' } |
| + | |
| + | subject { array.sort_by { |entry| order.apply_to(entry, :en) } } |
| + | |
| + | it { expect(subject.map(&:id)).to eq([3, 2, 1, 4]) } |
| + | |
| + | end |
| + | |
| + | end |
spec/unit/adapters/memory/query_spec.rb
+65
-0
| @@ | @@ -0,0 +1,65 @@ |
| + | require 'spec_helper' |
| + | |
| + | require_relative '../../../../lib/locomotive/steam/adapters/memory/dataset.rb' |
| + | require_relative '../../../../lib/locomotive/steam/adapters/memory/condition.rb' |
| + | require_relative '../../../../lib/locomotive/steam/adapters/memory/order.rb' |
| + | require_relative '../../../../lib/locomotive/steam/adapters/memory/query.rb' |
| + | |
| + | describe Locomotive::Steam::Adapters::Memory::Query do |
| + | |
| + | let(:entry_1) { OpenStruct.new(name: 'foo', id: 1) } |
| + | let(:entry_2) { OpenStruct.new(name: 'bar', id: 2) } |
| + | let(:entry_3) { OpenStruct.new(name: 'zone', id: 3) } |
| + | let(:records) { { 1 => entry_1, 2 => entry_2, 3 => entry_3 } } |
| + | let(:dataset) { Locomotive::Steam::Adapters::Memory::Dataset.new(:test) } |
| + | let(:locale) { :en } |
| + | |
| + | let(:query) { Locomotive::Steam::Adapters::Memory::Query } |
| + | |
| + | before { allow(dataset).to receive(:records).and_return(records) } |
| + | |
| + | describe '#limited' do |
| + | specify do |
| + | expect( |
| + | query.new(dataset, locale) do |
| + | limit(1) |
| + | end.all |
| + | ).to eq([entry_1]) |
| + | end |
| + | end |
| + | |
| + | describe '#order_by' do |
| + | |
| + | context 'asc' do |
| + | specify do |
| + | expect( |
| + | query.new(dataset, locale) do |
| + | order_by('name asc') |
| + | end.all.map(&:name) |
| + | ).to eq(['bar', 'foo', 'zone']) |
| + | end |
| + | end |
| + | |
| + | context 'desc' do |
| + | specify do |
| + | expect( |
| + | query.new(dataset, locale) do |
| + | order_by('name desc') |
| + | end.all.map(&:name) |
| + | ).to eq(['zone', 'foo', 'bar']) |
| + | end |
| + | end |
| + | end |
| + | |
| + | describe '#where' do |
| + | specify do |
| + | expect( |
| + | query.new(dataset, locale) do |
| + | where('name.eq' => 'foo'). |
| + | where('id.lt' => 2) |
| + | end.all.map(&:name) |
| + | ).to eq(['foo']) |
| + | end |
| + | end |
| + | |
| + | end |
spec/unit/adapters/memory_adapter_spec.rb
+28
-0
| @@ | @@ -0,0 +1,28 @@ |
| + | require 'spec_helper' |
| + | |
| + | require_relative '../../../lib/locomotive/steam/adapters/memory.rb' |
| + | |
| + | describe Locomotive::Steam::MemoryAdapter do |
| + | |
| + | let(:collection) { [OpenStruct.new(name: 'Hello world')] } |
| + | let(:mapper) { instance_double('Mapper', name: :test) } |
| + | let(:scope) { instance_double('Scope', locale: nil) } |
| + | let(:adapter) { Locomotive::Steam::MemoryAdapter.new(collection) } |
| + | |
| + | before { allow(mapper).to receive(:to_entity) { |arg| arg } } |
| + | |
| + | describe '#all' do |
| + | |
| + | subject { adapter.all(mapper, scope) } |
| + | it { expect(subject.size).to eq 1 } |
| + | |
| + | end |
| + | |
| + | describe '#query' do |
| + | |
| + | subject { adapter.query(mapper, scope) { where(name: 'Hello world') } } |
| + | it { expect(subject.size).to eq 1 } |
| + | |
| + | end |
| + | |
| + | end |
spec/unit/models/mapper_spec.rb
+46
-13
| @@ | @@ -2,14 +2,15 @@ require 'spec_helper' |
| 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, &block) } |
| + | let(:repository) { instance_double('Repository') } |
| + | let(:name) { 'pages' } |
| + | let(:options) { { entity: MyPage } } |
| + | let(:block) { nil } |
| + | let(:mapper) { Locomotive::Steam::Models::Mapper.new(name, options, repository, &block) } |
| describe '#localized attributes' do | |
| - | let(:block) { ->(_) { set_localized_attributes(:foo, :bar) } } |
| + | let(:block) { ->(_) { localized_attributes(:foo, :bar) } } |
| subject { mapper.localized_attributes } | |
| it { is_expected.to eq [:foo, :bar] } | |
| @@ | @@ -18,24 +19,56 @@ describe Locomotive::Steam::Models::Mapper do |
| describe '#to_entity' do | |
| - | let(:block) { ->(_) { set_localized_attributes(:title) } } |
| - | let(:attributes) { { title: { 'en' => 'Hello world' } } } |
| - | |
| subject { mapper.to_entity(attributes) } | |
| - | it { expect(subject.attributes[:title].class).to eq Locomotive::Steam::Models::I18nField } |
| - | it { expect(subject.attributes[:title][:en]).to eq('Hello world') } |
| - | context 'string value for the localized field' do |
| + | describe 'default attributes' do |
| let(:attributes) { { title: 'Hello world' } } | |
| + | let(:repository) { instance_double('Repository', my_site: 42) } |
| + | let(:block) { ->(_) { default_attribute(:site, -> (repository) { repository.my_site }) } } |
| + | |
| + | it { expect(subject.site).to eq 42 } |
| + | |
| + | end |
| + | |
| + | describe 'association' do |
| + | |
| + | let(:attributes) { { parents: [instance_double('Page', title: 'Hello world')] } } |
| + | let(:klass) { instance_double('RepositoryKlass')} |
| + | let(:block) { ->(_) { association(:parents, BlankRepository) } } |
| + | |
| + | it { expect(subject.parents).not_to eq nil } |
| + | |
| + | end |
| + | describe 'localized attributes' do |
| + | |
| + | let(:block) { ->(_) { localized_attributes(:title) } } |
| + | let(:attributes) { { title: { 'en' => 'Hello world' } } } |
| + | |
| + | it { expect(subject.attributes[:title].class).to eq Locomotive::Steam::Models::I18nField } |
| it { expect(subject.attributes[:title][:en]).to eq('Hello world') } | |
| - | it { expect(subject.attributes[:title][:fr]).to eq('Hello world') } |
| + | |
| + | context 'string value for the localized field' do |
| + | |
| + | let(:attributes) { { title: 'Hello world' } } |
| + | |
| + | it { expect(subject.attributes[:title][:en]).to eq('Hello world') } |
| + | it { expect(subject.attributes[:title][:fr]).to eq('Hello world') } |
| + | |
| + | end |
| end | |
| end | |
| - | class MyPage < Struct.new(:attributes); end |
| + | class MyPage |
| + | include Locomotive::Steam::Models::Entity |
| + | attr_accessor :site |
| + | end |
| + | |
| + | class BlankRepository < Struct.new(:adapter) |
| + | attr_accessor :parent |
| + | end |
| end | |