searching for content entries with a date/date time/belongs_to criteria works now + get correctly redirected when submitting an invalid entry (when mounted in the Engine) + Pony requires symbolized keys options

did committed Mar 31, 2016
commit fd93894ce9e35c701a8a9dadb2fa414df29e63f1
Showing 17 changed files with 192 additions and 23 deletions
locomotive/steam/adapters/filesystem.rb b/lib/locomotive/steam/adapters/filesystem.rb +4 -0
@@ @@ -68,6 +68,10 @@ module Locomotive::Steam
''
end
+ def make_id(value)
+ value
+ end
+
def count(mapper, scope, &block)
query(mapper, scope, &block).count
end
locomotive/steam/adapters/mongodb.rb b/lib/locomotive/steam/adapters/mongodb.rb +6 -1
@@ @@ -27,7 +27,8 @@ module Locomotive::Steam
end
def find(mapper, scope, id)
- query(mapper, scope) { where(_id: BSON::ObjectId.from_string(id)) }.first
+ _id = make_id(id)
+ query(mapper, scope) { where(_id: _id) }.first
end
def create(mapper, scope, entity)
@@ @@ -50,6 +51,10 @@ module Locomotive::Steam
name.to_sym.__send__(operator.to_sym)
end
+ def make_id(id)
+ BSON::ObjectId.from_string(id)
+ end
+
def base_url(mapper, scope, entity = nil)
return nil if scope.site.nil?
locomotive/steam/entities/content_entry.rb b/lib/locomotive/steam/entities/content_entry.rb +5 -1
@@ @@ -83,7 +83,7 @@ module Locomotive::Steam
_attributes += content_type.persisted_field_names
_attributes.inject({}) do |hash, name|
- hash[name.to_s] = send(name)
+ hash[name.to_s] = send(name) rescue nil
hash
end.tap do |hash|
# errors?
@@ @@ -184,6 +184,10 @@ module Locomotive::Steam
base.blank? ? filename : "#{base}/#{filename}"
end
+ def to_hash
+ { 'url' => url, 'filename' => filename, 'size' => size, 'updated_at' => updated_at }
+ end
+
def to_json
url
end
locomotive/steam/liquid/tags/action.rb b/lib/locomotive/steam/liquid/tags/action.rb +10 -5
@@ @@ -12,11 +12,16 @@ module Locomotive
#
# Usage:
#
- # {% action "" %}
- # {% for post in blog.posts %}
- # {{ post.title }}
- # {% endfor %}
- # {% endconsume %}
+ # {% action "My javascript action" %}
+ # var lastPost = allEntries('posts', { 'posted_at.lte': getProp('today'), published: true, order_by: 'posted_at desc' })[0];
+ # var views = lastPost.views + 1;
+ #
+ # updateEntry('posts', lastPost._id, { views: views });
+ #
+ # setProp('views', views);
+ # {% endaction %}
+ #
+ # <p>Number of views for the last published post: {{ views }}</p>
#
class Action < ::Liquid::Block
locomotive/steam/middlewares/entry_submission.rb b/lib/locomotive/steam/middlewares/entry_submission.rb +1 -1
@@ @@ -59,7 +59,7 @@ module Locomotive::Steam
if error_location =~ HTTP_REGEXP
redirect_to error_location
else
- env['PATH_INFO'] = error_location
+ env['PATH_INFO'] = make_local_path(error_location)
store_in_liquid(entry)
self.next
end
locomotive/steam/middlewares/helpers.rb b/lib/locomotive/steam/middlewares/helpers.rb +8 -0
@@ @@ -37,6 +37,14 @@ module Locomotive::Steam
path
end
+ # make sure the location passed in parameter doesn't
+ # include the "mounted_on" parameter.
+ # If so, returns the location without the "mounted_on" string.
+ def make_local_path(location)
+ return location if mounted_on.blank?
+ location.gsub(Regexp.new('^' + mounted_on), '')
+ end
+
def mounted_on
request.env['steam.mounted_on']
end
locomotive/steam/middlewares/renderer.rb b/lib/locomotive/steam/middlewares/renderer.rb +2 -1
@@ @@ -72,7 +72,8 @@ module Locomotive::Steam
'now' => Time.zone.now,
'today' => Date.today,
'mode' => Locomotive::Steam.configuration.mode,
- 'wagon' => Locomotive::Steam.configuration.mode == :test
+ 'wagon' => Locomotive::Steam.configuration.mode == :test,
+ 'live_editing' => !!env['steam.live_editing']
}
end
locomotive/steam/repositories/content_entry_repository.rb b/lib/locomotive/steam/repositories/content_entry_repository.rb +23 -4
@@ @@ -131,7 +131,7 @@ module Locomotive
end
def prepare_conditions(*conditions)
- _conditions = Conditions.new(conditions.first, self.content_type.fields).prepare
+ _conditions = Conditions.new(adapter, conditions.first, self.content_type.fields).prepare
super({ _visible: true }, _conditions)
end
@@ @@ -185,8 +185,10 @@ module Locomotive
class Conditions
- def initialize(conditions = {}, fields)
- @conditions, @fields, @operators = conditions.try(:with_indifferent_access) || {}, fields, {}
+ def initialize(adapter, conditions = {}, fields)
+ @adapter = adapter
+ @conditions = conditions.try(:with_indifferent_access) || {}
+ @fields, @operators = fields, {}
@conditions.each do |name, value|
_name, operator = name.to_s.split('.')
@@ @@ -200,6 +202,9 @@ module Locomotive
field.select_options.by_name(value).try(:_id)
end
+ # date
+ _prepare(@fields.dates_and_date_times) { |field, value| value_to_date(value, field.type) }
+
# belongs_to
_prepare(@fields.belongs_to) { |field, value| value_to_id(value) }
@@ @@ -233,11 +238,25 @@ module Locomotive
end
def value_to_id(value)
- if value.respond_to?(:each) # array
+ _value = if value.is_a?(Hash)
+ value['_id'] || value[:_id]
+ elsif value.respond_to?(:each) # array
values_to_ids(value)
else
value.respond_to?(:_id) ? value._id : value
end
+
+ @adapter.make_id(_value)
+ end
+
+ def value_to_date(value, type)
+ _value = if value.is_a?(String)
+ Chronic.time_class = Time.zone
+ Chronic.parse(value)
+ else
+ value
+ end
+ type == :date ? _value.to_date : _value.to_datetime
end
def values_to_ids(value)
locomotive/steam/repositories/content_type_field_repository.rb b/lib/locomotive/steam/repositories/content_type_field_repository.rb +4 -0
@@ @@ -26,6 +26,10 @@ module Locomotive
query { where(type: :file) }.all
end
+ def dates_and_date_times
+ query { where(k(:type, :in) => %i(date date_time)) }.all
+ end
+
def belongs_to
query { where(type: :belongs_to) }.all
end
locomotive/steam/services/action_service.rb b/lib/locomotive/steam/services/action_service.rb +1 -1
@@ @@ -51,7 +51,7 @@ module Locomotive
end
def send_email_lambda(liquid_context)
- -> (options) { email.send_email(options.with_indifferent_access, liquid_context) }
+ -> (options) { !!email.send_email(options, liquid_context) }
end
def get_prop_lambda(liquid_context)
locomotive/steam/services/email_service.rb b/lib/locomotive/steam/services/email_service.rb +3 -2
@@ @@ -8,12 +8,12 @@ module Locomotive
attr_accessor_initialize :page_finder_service, :liquid_parser, :asset_host, :simulation
def send_email(options, context)
- build_body(options, context, options.delete(:html))
+ build_body(options.symbolize_keys!, context, options.delete(:html))
extract_attachment(options)
options[:via] ||= :smtp
- options[:via_options] ||= options.delete(:smtp)
+ options[:via_options] ||= options.delete(:smtp).try(:symbolize_keys)
log(options, simulation)
@@ @@ -80,6 +80,7 @@ module Locomotive
message << "From: #{options[:from]}"
message << "To: #{options[:to]}"
message << "Subject: #{options[:subject]}"
+ message << "Attachments: #{options[:attachments]}"
message << "-----------"
message << (options[:body] || options[:html_body]).gsub("\n", "\n\t")
message << "-----------"
spec/unit/adapters/filesystem_adapter_spec.rb +10 -0
@@ @@ -68,4 +68,14 @@ describe Locomotive::Steam::FilesystemAdapter do
end
+ describe '#make_id' do
+
+ let(:id) { '42' }
+
+ subject { adapter.make_id(id) }
+
+ it { is_expected.to eq('42') }
+
+ end
+
end
spec/unit/adapters/mongodb_adapter_spec.rb +18 -0
@@ @@ -14,4 +14,22 @@ describe Locomotive::Steam::MongoDBAdapter do
end
+ describe '#make_id' do
+
+ let(:id) { '56fd9f48a2f42217744a85d7' }
+
+ subject { adapter.make_id(id) }
+
+ it { is_expected.to eq(BSON::ObjectId.from_string('56fd9f48a2f42217744a85d7')) }
+
+ context 'passing a BSON::ObjectId' do
+
+ let(:id) { BSON::ObjectId.from_string('56fd9f48a2f42217744a85d7') }
+
+ it { is_expected.to eq(BSON::ObjectId.from_string('56fd9f48a2f42217744a85d7')) }
+
+ end
+
+ end
+
end
spec/unit/entities/content_entry_spec.rb +19 -0
@@ @@ -99,6 +99,25 @@ describe Locomotive::Steam::ContentEntry do
end
+ describe '#as_json' do
+
+ let(:fields) { [instance_double('TitleField', name: :title, type: :string), instance_double('PictureField', name: :picture, type: :file, localized: true)] }
+ let(:attributes) { { id: 42, title: 'Hello world', _slug: 'hello-world', picture: Locomotive::Steam::Models::I18nField.new(:picture, fr: 'foo.png', en: 'bar.png'), custom_fields_recipe: ['hello', 'world'], _type: 'Entry' } }
+ let(:decorated) { Locomotive::Steam::Decorators::I18nDecorator.new(content_entry, :fr, :en) }
+
+ before do
+ allow(type).to receive(:fields_by_name).and_return({ title: fields.first, picture: fields.last })
+ allow(type).to receive(:persisted_field_names).and_return([:title, :picture])
+ allow(content_entry).to receive(:localized_attributes).and_return({ picture: true })
+ allow(content_entry).to receive(:base_url).and_return('/assets')
+ end
+
+ subject { decorated.as_json }
+
+ it { expect(subject['picture']['url']).to eq '/assets/foo.png' }
+
+ end
+
describe 'dynamic attributes' do
let(:field_type) { :string }
spec/unit/middlewares/helpers_spec.rb +29 -0
@@ @@ -7,6 +7,35 @@ describe Locomotive::Steam::Middlewares::Helpers do
let(:middleware) { Class.new { include Locomotive::Steam::Middlewares::Helpers } }
let(:instance) { middleware.new }
+ describe '#make_local_path' do
+
+ let(:mounted_on) { nil }
+ let(:location) { '/foo/bar' }
+
+ before { allow(instance).to receive(:mounted_on).and_return(mounted_on) }
+
+ subject { instance.make_local_path(location) }
+
+ it { is_expected.to eq '/foo/bar' }
+
+ context 'mounted_on is not blank' do
+
+ let(:mounted_on) { '/my_app' }
+
+ it { is_expected.to eq '/foo/bar' }
+
+ context 'path including mounted_on' do
+
+ let(:location) { '/my_app/foo/bar' }
+
+ it { is_expected.to eq '/foo/bar' }
+
+ end
+
+ end
+
+ end
+
describe '#redirect_to' do
subject { instance.redirect_to(location)[1]['Location'] }
spec/unit/repositories/content_entry_repository_spec.rb +39 -7
@@ @@ -4,7 +4,7 @@ require_relative '../../../lib/locomotive/steam/adapters/filesystem.rb'
describe Locomotive::Steam::ContentEntryRepository do
- let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: []) }
+ let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: []) }
let(:type) { build_content_type('Articles', label_field_name: :title, localized_names: [:title], fields: _fields, fields_by_name: { title: instance_double('Field', name: :title, type: :string) }, fields_with_default: []) }
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 }
@@ @@ -274,7 +274,7 @@ describe Locomotive::Steam::ContentEntryRepository do
let(:other_type) { build_content_type('Authors', _id: 2, label_field_name: :name, fields: _fields, fields_by_name: { name: instance_double('Field', name: :name, type: :string) }, fields_with_default: []) }
let(:other_entries) { [{ content_type_id: 2, _id: 'john-doe', name: 'John Doe' }] }
- let(:type_repository) { instance_double('ArticleBelongsToRepository', selects: [], belongs_to: [], many_to_many: []) }
+ let(:type_repository) { instance_double('ArticleBelongsToRepository', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: []) }
before do
allow(type).to receive(:fields).and_return(type_repository)
@@ @@ -307,7 +307,7 @@ describe Locomotive::Steam::ContentEntryRepository do
]
}
- let(:type_repository) { instance_double('AuthorRepository', selects: [], belongs_to: [], many_to_many: []) }
+ let(:type_repository) { instance_double('AuthorRepository', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: []) }
before do
allow(type).to receive(:fields).and_return(type_repository)
@@ @@ -340,7 +340,7 @@ describe Locomotive::Steam::ContentEntryRepository do
]
}
- let(:type_repository) { instance_double('AuthorRepository', selects: [], belongs_to: [], many_to_many: []) }
+ let(:type_repository) { instance_double('AuthorRepository', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: []) }
before do
allow(type).to receive(:fields).and_return(type_repository)
@@ @@ -373,18 +373,42 @@ describe Locomotive::Steam::ContentEntryRepository do
let(:option) { instance_double('Option', _id: 42)}
let(:options) { instance_double('OptionRepository', by_name: option) }
let(:field) { instance_double('SelectField', name: 'category', persisted_name: 'category_id', select_options: options) }
- let(:_fields) { instance_double('Fields', selects: [field], belongs_to: [], many_to_many: []) }
+ let(:_fields) { instance_double('Fields', selects: [field], belongs_to: [], many_to_many: [], dates_and_date_times: []) }
let(:conditions) { { 'category' => value } }
it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'category_id' => 42 }, nil]) }
end
+ context 'date fields' do
+
+ let(:value) { '2009/09/10' }
+ let(:field) { instance_double('DateField', name: 'launched_at', persisted_name: 'launched_at', type: :date) }
+ let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: [field]) }
+ let(:conditions) { { 'launched_at' => value } }
+
+ it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'launched_at' => Date.parse('2009/09/10') }, nil]) }
+
+ end
+
+ context 'date time fields' do
+
+ before { Time.zone = 'Paris' }
+
+ let(:value) { '2007/06/29 21:15:00' }
+ let(:field) { instance_double('DateField', name: 'launched_at', persisted_name: 'launched_at', type: :date_time) }
+ let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [], dates_and_date_times: [field]) }
+ let(:conditions) { { 'launched_at' => value } }
+
+ it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'launched_at' => Time.zone.parse('2007/06/29 21:15:00').to_datetime }, nil]) }
+
+ end
+
context 'belongs_to fields' do
let(:value) { 42 }
let(:field) { instance_double('BelongsToField', name: 'person', persisted_name: 'person_id') }
- let(:_fields) { instance_double('Fields', selects: [], belongs_to: [field], many_to_many: []) }
+ let(:_fields) { instance_double('Fields', selects: [], belongs_to: [field], many_to_many: [], dates_and_date_times: []) }
let(:conditions) { { 'person' => value } }
it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'person_id' => 42 }, nil]) }
@@ @@ -397,6 +421,14 @@ describe Locomotive::Steam::ContentEntryRepository do
end
+ context 'the target is a hash' do
+
+ let(:value) { { '_id' => 42 } }
+
+ it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'person_id' => 42 }, nil]) }
+
+ end
+
context 'the target value is an arry of content entry' do
let(:value) { [instance_double('TargetContentEntry', _id: 1), instance_double('TargetContentEntry', _id: 2)] }
@@ @@ -419,7 +451,7 @@ describe Locomotive::Steam::ContentEntryRepository do
let(:value) { 42 }
let(:field) { instance_double('ManyToManyField', name: 'tags', persisted_name: 'tag_ids') }
- let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [field]) }
+ let(:_fields) { instance_double('Fields', selects: [], belongs_to: [], many_to_many: [field], dates_and_date_times: []) }
let(:conditions) { { 'tags.in' => value } }
it { is_expected.to eq([{ _visible: true, content_type_id: 1, 'tag_ids.in' => [42] }, nil]) }
spec/unit/repositories/content_type_field_repository_spec.rb +10 -0
@@ @@ -70,4 +70,14 @@ describe Locomotive::Steam::ContentTypeFieldRepository do
end
+ describe '#dates_and_date_times' do
+
+ let(:collection) { [{ name: 'name', type: 'string' }, { name: 'launched_at', type: 'date' }, { name: 'updated_at', type: 'date_time' }] }
+
+ subject { repository.dates_and_date_times }
+
+ it { expect(subject.map(&:name)).to eq(['launched_at', 'updated_at']) }
+
+ end
+
end