can now create or delete an entity with the MongoDB adapter

did committed Jul 08, 2015
commit 4f681d2a11bc83693a4a1f64384a7df7b9d42bd5
Showing 20 changed files with 252 additions and 68 deletions
locomotive/steam/adapters/filesystem.rb b/lib/locomotive/steam/adapters/filesystem.rb +5 -0
@@ @@ -40,6 +40,11 @@ module Locomotive::Steam
dataset.insert(entity)
sanitizer.apply_to_entity_with_dataset(entity, dataset)
end
+ entity
+ end
+
+ def delete(mapper, scope, entity)
+ # TODO: to be implemented
end
def find(mapper, scope, id)
locomotive/steam/adapters/filesystem/yaml_loaders/content_entry.rb b/lib/locomotive/steam/adapters/filesystem/yaml_loaders/content_entry.rb +2 -2
@@ @@ -29,13 +29,13 @@ module Locomotive
end
def modify_for_selects(attributes)
- content_type.selects.each do |field|
+ content_type.select_fields.each do |field|
attributes[:"#{field.name}_id"] = attributes.delete(field.name.to_sym)
end
end
def modify_for_associations(attributes)
- content_type.associations.each do |field|
+ content_type.association_fields.each do |field|
case field.type
when :belongs_to
modify_belongs_to_association(field, attributes)
locomotive/steam/adapters/mongodb.rb b/lib/locomotive/steam/adapters/mongodb.rb +4 -0
@@ @@ -32,6 +32,10 @@ module Locomotive::Steam
command(mapper).insert(entity)
end
+ def delete(mapper, scope, entity)
+ command(mapper).delete(entity)
+ end
+
def key(name, operator)
name.to_sym.__send__(operator.to_sym)
end
locomotive/steam/adapters/mongodb/command.rb b/lib/locomotive/steam/adapters/mongodb/command.rb +31 -0
@@ @@ -0,0 +1,31 @@
+ module Locomotive::Steam
+ module Adapters
+ module MongoDB
+
+ class Command
+
+ def initialize(collection, mapper)
+ @collection = collection
+ @mapper = mapper
+ end
+
+ def insert(entity)
+ # make sure the entity gets a valid id
+ entity[:_id] ||= BSON::ObjectId.new
+
+ serialized_entity = @mapper.serialize(entity)
+
+ @collection.insert(serialized_entity)
+
+ entity
+ end
+
+ def delete(entity)
+ @collection.find(_id: entity._id).remove if entity._id
+ end
+
+ end
+
+ end
+ end
+ end
locomotive/steam/entities/content_entry.rb b/lib/locomotive/steam/entities/content_entry.rb +4 -0
@@ @@ -69,6 +69,10 @@ module Locomotive::Steam
end
end
+ def serialize
+ super.merge(content_type_id: content_type_id)
+ end
+
def to_liquid
Locomotive::Steam::Liquid::Drops::ContentEntry.new(self)
end
locomotive/steam/entities/content_type.rb b/lib/locomotive/steam/entities/content_type.rb +3 -2
@@ @@ -5,7 +5,8 @@ module Locomotive::Steam
include Locomotive::Steam::Models::Entity
extend Forwardable
- def_delegators :fields, :associations, :selects
+ def_delegator :fields, :associations, :association_fields
+ def_delegator :fields, :selects, :select_fields
def initialize(attributes = {})
super({
@@ @@ -27,7 +28,7 @@ module Locomotive::Steam
end
def localized_names
- fields.localized_names + selects.map(&:name)
+ fields.localized_names + select_fields.map(&:name)
end
def label_field_name
locomotive/steam/models/associations/belongs_to.rb b/lib/locomotive/steam/models/associations/belongs_to.rb +12 -3
@@ @@ -4,12 +4,21 @@ module Locomotive::Steam
class BelongsToAssociation < ReferencedAssociation
def __load__
- name = @options[:association_name]
- target_id = @entity[:"#{name}_id"]
+ target_id = @entity[__target_key__]
target = @repository.find(target_id)
# replace the proxy class by the real target entity
- @entity[name] = target
+ @entity[__name__] = target
+ end
+
+ def __serialize__(attributes)
+ attributes[__target_key__] = attributes[__name__].try(:_id)
+
+ attributes.delete(__name__)
+ end
+
+ def __target_key__
+ :"#{__name__}_id"
end
end
locomotive/steam/models/associations/embedded.rb b/lib/locomotive/steam/models/associations/embedded.rb +4 -0
@@ @@ -33,6 +33,10 @@ module Locomotive::Steam
@repository.send(:"#{name}=", entity)
end
+ def __serialize__(entity)
+ # TODO: not implemented yet
+ end
+
def method_missing(name, *args, &block)
@repository.send(name, *args, &block)
end
locomotive/steam/models/associations/many_to_many.rb b/lib/locomotive/steam/models/associations/many_to_many.rb +12 -2
@@ @@ -4,10 +4,9 @@ module Locomotive::Steam
class ManyToManyAssociation < ReferencedAssociation
def __load__
- source_key = :"#{@options[:association_name].to_s.singularize}_ids"
key = @repository.k(:_id, :in)
- @repository.local_conditions[key] = @entity[source_key]
+ @repository.local_conditions[key] = @entity[__target_key__]
# use order_by from options as the default one for further queries
@repository.local_conditions[:order_by] = @options[:order_by] unless @options[:order_by].blank?
@@ @@ -16,6 +15,17 @@ module Locomotive::Steam
@repository
end
+ def __serialize__(attributes)
+ attributes[__target_key__] = attributes[__name__].try(:map, &:_id)
+
+ attributes.delete(__name__)
+ end
+
+ def __target_key__
+ :"#{__name__.to_s.singularize}_ids"
+ end
+
+
end
end
locomotive/steam/models/associations/referenced.rb b/lib/locomotive/steam/models/associations/referenced.rb +8 -0
@@ @@ -19,6 +19,10 @@ module Locomotive::Steam
@options = options
end
+ def __name__
+ @options[:association_name]
+ end
+
def __attach__(entity)
@entity = entity
end
@@ @@ -27,6 +31,10 @@ module Locomotive::Steam
# needs implementation
end
+ def __serialize__(entity)
+ # needs implementation
+ end
+
def __call_block_once__
# setup the repository if custom configuration from the
# repository for instance.
locomotive/steam/models/entity.rb b/lib/locomotive/steam/models/entity.rb +7 -1
@@ @@ -5,7 +5,7 @@ module Locomotive::Steam
include Locomotive::Steam::Models::Concerns::Validation
- attr_accessor :attributes, :localized_attributes, :base_url
+ attr_accessor :attributes, :associations, :localized_attributes, :base_url
def initialize(attributes)
@attributes = attributes.with_indifferent_access
@@ @@ -14,6 +14,8 @@ module Locomotive::Steam
def method_missing(name, *args, &block)
if attributes.include?(name)
self[name]
+ elsif name.to_s.end_with?('=') && attributes.include?(name.to_s.chop)
+ self[name.to_s.chop] = args.first
else
super
end
@@ @@ -35,6 +37,10 @@ module Locomotive::Steam
attributes[name.to_sym]
end
+ def serialize
+ attributes.dup
+ end
+
end
end
end
locomotive/steam/models/i18n_field.rb b/lib/locomotive/steam/models/i18n_field.rb +4 -0
@@ @@ -36,6 +36,10 @@ module Locomotive::Steam
alias :__translations__ :translations
+ def serialize(attributes)
+ attributes[@name] = @translations
+ end
+
end
end
locomotive/steam/models/mapper.rb b/lib/locomotive/steam/models/mapper.rb +25 -7
@@ @@ -48,11 +48,12 @@ module Locomotive::Steam
def to_entity(attributes)
entity_klass.new(deserialize(attributes)).tap do |entity|
- attach_entity_to_associations(entity)
-
set_default_attributes(entity)
entity.localized_attributes = @localized_attributes_hash || {}
+ entity.associations = {}
+
+ attach_entity_to_associations(entity)
entity.base_url = @repository.base_url(entity)
end
@@ @@ -64,6 +65,23 @@ module Locomotive::Steam
attributes
end
+ def serialize(entity)
+ entity.serialize.tap do |attributes|
+ # scope
+ @repository.scope.apply(attributes)
+
+ # localized fields
+ @localized_attributes.each do |name|
+ entity.send(name).serialize(attributes)
+ end
+
+ # association name -> id (belongs_to) or ids (many_to_many)
+ (entity.associations || {}).each do |name, association|
+ association.__serialize__(attributes)
+ end
+ end
+ end
+
def entity_klass
options[:entity]
end
@@ @@ -88,10 +106,7 @@ module Locomotive::Steam
@associations.each do |(type, name, repository_klass, options, block)|
klass = ASSOCIATION_CLASSES[type]
- _options = options.merge({
- association_name: name,
- mapper_name: self.name
- })
+ _options = options.merge(association_name: name, mapper_name: self.name)
attributes[name] = (if type == :embedded
klass.new(repository_klass, attributes[name], @repository.scope, _options)
@@ @@ -103,7 +118,10 @@ module Locomotive::Steam
def attach_entity_to_associations(entity)
@associations.each do |(type, name, _)|
- entity[name].__attach__(entity)
+ association = entity[name]
+ association.__attach__(entity)
+
+ entity.associations[name] = association
end
end
locomotive/steam/models/repository.rb b/lib/locomotive/steam/models/repository.rb +4 -0
@@ @@ -31,6 +31,10 @@ module Locomotive::Steam
adapter.create(mapper, scope, entity)
end
+ def delete(entity)
+ adapter.delete(mapper, scope, entity)
+ end
+
def find(id)
adapter.find(mapper, scope, id)
end
locomotive/steam/models/scope.rb b/lib/locomotive/steam/models/scope.rb +4 -0
@@ @@ -17,6 +17,10 @@ module Locomotive::Steam
site.try(:locales)
end
+ def apply(attributes)
+ attributes['site_id'] = @site._id
+ end
+
def to_key
(@site ? ['site', @site._id] : []).tap do |base|
@context.each do |name, object|
locomotive/steam/repositories/content_entry_repository.rb b/lib/locomotive/steam/repositories/content_entry_repository.rb +1 -1
@@ @@ -140,7 +140,7 @@ module Locomotive
end
def add_associations_to_mapper(mapper)
- self.content_type.associations.each do |field|
+ self.content_type.association_fields.each do |field|
mapper.association(field.type, field.name, self.class, field.association_options, &method(:prepare_repository_for_association))
end
end
spec/integration/repositories/content_entry_repository_spec.rb +45 -42
@@ @@ -13,48 +13,48 @@ describe Locomotive::Steam::ContentEntryRepository do
let(:repository) { described_class.new(adapter, site, locale, type_repository).with(type) }
let(:type) { type_repository.by_slug('bands') }
- # describe '#all' do
- # subject { repository.all }
- # it { expect(subject.size).to eq 3 }
- # end
-
- # describe '#count' do
- # subject { repository.count }
- # it { is_expected.to eq 3 }
- # end
-
- # describe '#by_slug' do
- # subject { repository.by_slug('alice-in-chains') }
- # it { expect(subject.name).to eq 'Alice in Chains' }
- # end
-
- # describe '#exists?' do
- # subject { repository.exists?(featured: true) }
- # it { is_expected.to eq true }
- # end
-
- # describe '#find' do
- # subject { repository.find(entry_id) }
- # it { expect(subject.name).to eq 'Pearl Jam' }
- # end
-
- # describe '#next' do
- # let(:entry) { repository.find(entry_id) }
- # subject { repository.next(entry) }
- # it { expect(subject.name).to eq 'The who' }
- # end
-
- # describe '#previous' do
- # let(:entry) { repository.find(entry_id) }
- # subject { repository.previous(entry) }
- # it { expect(subject.name).to eq 'Alice in Chains' }
- # end
-
- # describe '#group_by_select_option' do
- # subject { repository.group_by_select_option(:kind) }
- # it { expect(subject.map { |h| h[:name] }).to eq(%w(grunge rock country)) }
- # it { expect(subject.map { |h| h[:entries].size }).to eq([2, 1, 0]) }
- # end
+ describe '#all' do
+ subject { repository.all }
+ it { expect(subject.size).to eq 3 }
+ end
+
+ describe '#count' do
+ subject { repository.count }
+ it { is_expected.to eq 3 }
+ end
+
+ describe '#by_slug' do
+ subject { repository.by_slug('alice-in-chains') }
+ it { expect(subject.name).to eq 'Alice in Chains' }
+ end
+
+ describe '#exists?' do
+ subject { repository.exists?(featured: true) }
+ it { is_expected.to eq true }
+ end
+
+ describe '#find' do
+ subject { repository.find(entry_id) }
+ it { expect(subject.name).to eq 'Pearl Jam' }
+ end
+
+ describe '#next' do
+ let(:entry) { repository.find(entry_id) }
+ subject { repository.next(entry) }
+ it { expect(subject.name).to eq 'The who' }
+ end
+
+ describe '#previous' do
+ let(:entry) { repository.find(entry_id) }
+ subject { repository.previous(entry) }
+ it { expect(subject.name).to eq 'Alice in Chains' }
+ end
+
+ describe '#group_by_select_option' do
+ subject { repository.group_by_select_option(:kind) }
+ it { expect(subject.map { |h| h[:name] }).to eq(%w(grunge rock country)) }
+ it { expect(subject.map { |h| h[:entries].size }).to eq([2, 1, 0]) }
+ end
describe '#create' do
@@ @@ -65,6 +65,9 @@ describe Locomotive::Steam::ContentEntryRepository do
subject { repository.create(entry) }
it { expect { subject }.to change { repository.all.size } }
+ it { expect(subject._id).not_to eq nil }
+
+ after { repository.delete(entry) }
end
spec/unit/adapters/filesystem/yaml_loaders/content_entry_spec.rb +3 -3
@@ @@ -6,7 +6,7 @@ require_relative '../../../../../lib/locomotive/steam/adapters/filesystem/yaml_l
describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do
let(:site_path) { default_fixture_site_path }
- let(:content_type) { instance_double('Bands', _id: 42, slug: 'bands', associations: [], selects: []) }
+ let(:content_type) { instance_double('Bands', _id: 42, slug: 'bands', association_fields: [], select_fields: []) }
let(:scope) { instance_double('Scope', locale: :en, context: { content_type: content_type }) }
let(:loader) { described_class.new(site_path) }
@@ @@ -23,7 +23,7 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do
context 'a content type with a belongs_to field' do
let(:field) { instance_double('Field', name: 'band', type: :belongs_to) }
- let(:content_type) { instance_double('Songs', slug: 'songs', associations: [field], selects: []) }
+ let(:content_type) { instance_double('Songs', slug: 'songs', association_fields: [field], select_fields: []) }
it 'adds a new attribute for the foreign key' do
expect(subject.first[:band_id]).to eq 'pearl-jam'
@@ @@ -36,7 +36,7 @@ describe Locomotive::Steam::Adapters::Filesystem::YAMLLoaders::ContentEntry do
context 'a content type with a select field' do
let(:field) { instance_double('Field', name: 'kind', type: :select) }
- let(:content_type) { instance_double('Bands', slug: 'bands', selects: [field], associations: []) }
+ let(:content_type) { instance_double('Bands', slug: 'bands', select_fields: [field], association_fields: []) }
it 'adds a new attribute for the foreign key' do
expect(subject.first[:kind_id]).to eq 'grunge'
spec/unit/models/mapper_spec.rb +70 -1
@@ @@ -2,7 +2,9 @@ require 'spec_helper'
describe Locomotive::Steam::Models::Mapper do
- let(:repository) { instance_double('Repository', base_url: '') }
+ let(:adapter) { instance_double('Adapter') }
+ let(:scope) { instance_double('SimpleScope', apply: true) }
+ let(:repository) { instance_double('Repository', scope: scope, base_url: '') }
let(:name) { 'pages' }
let(:options) { { entity: MyPage } }
let(:block) { nil }
@@ @@ -17,6 +19,68 @@ describe Locomotive::Steam::Models::Mapper do
end
+ describe '#serialize' do
+
+ let(:options) { { entity: MyArticle } }
+ let(:attributes) { { title: 'Hello world', body: 'Lorem ipsum', published_at: DateTime.parse('2007/06/29 00:00:00') } }
+ let(:entity) { mapper.to_entity(attributes) }
+
+ subject { mapper.serialize(entity) }
+
+ it { expect(subject).to eq('title' => 'Hello world', 'body' => 'Lorem ipsum', 'published_at' => DateTime.parse('2007/06/29 00:00:00')) }
+
+ describe 'association' do
+
+ let(:repository) { instance_double('AuthorRepository', scope: scope, adapter: adapter, base_url: '') }
+
+ describe 'belongs_to' do
+
+ let(:block) { ->(_) { belongs_to_association(:author, BlankRepository) } }
+
+ context 'no object' do
+
+ let(:attributes) { { author_id: nil } }
+
+ it { expect(subject).to eq('author_id' => nil) }
+
+ end
+
+ context 'existing object' do
+
+ before { entity.author = instance_double('Author', _id: 1) }
+
+ it { expect(subject).to eq('title' => 'Hello world', 'author_id' => 1, 'body' => 'Lorem ipsum', 'published_at' => DateTime.parse('2007/06/29 00:00:00')) }
+
+ end
+
+ end
+
+ describe 'many_to_many' do
+
+ let(:block) { ->(_) { many_to_many_association(:authors, BlankRepository) } }
+
+ context 'no object' do
+
+ let(:attributes) { { author_ids: nil } }
+
+ it { expect(subject).to eq('author_ids' => nil) }
+
+ end
+
+ context 'existing object' do
+
+ before { entity.authors = [instance_double('Author', _id: 1), instance_double('Author', _id: 2)] }
+
+ it { expect(subject).to eq('title' => 'Hello world', 'author_ids' => [1, 2], 'body' => 'Lorem ipsum', 'published_at' => DateTime.parse('2007/06/29 00:00:00')) }
+
+ end
+
+ end
+
+ end
+
+ end
+
describe '#to_entity' do
subject { mapper.to_entity(attributes) }
@@ @@ -68,6 +132,11 @@ describe Locomotive::Steam::Models::Mapper do
attr_accessor :site
end
+ class MyArticle
+ include Locomotive::Steam::Models::Entity
+ attr_accessor :site
+ end
+
class BlankRepository < Struct.new(:adapter)
attr_accessor :page, :scope
end
spec/unit/repositories/content_entry_repository_spec.rb +4 -4
@@ @@ -242,7 +242,7 @@ describe Locomotive::Steam::ContentEntryRepository do
describe 'belongs_to' do
let(:field) { instance_double('Field', name: :author, type: :belongs_to, association_options: { target_id: 2 }) }
- let(:type) { build_content_type('Articles', label_field_name: :title, associations: [field]) }
+ let(:type) { build_content_type('Articles', label_field_name: :title, association_fields: [field]) }
let(:entries) { [{ content_type_id: 1, title: 'Hello world', author_id: 'john-doe' }] }
let(:other_type) { build_content_type('Authors', _id: 2, label_field_name: :name, fields_by_name: { name: instance_double('Field', name: :name, type: :string) }) }
let(:other_entries) { [{ content_type_id: 2, _id: 'john-doe', name: 'John Doe' }] }
@@ @@ -269,7 +269,7 @@ describe Locomotive::Steam::ContentEntryRepository do
describe 'has_many' do
let(:field) { instance_double('Field', name: :articles, type: :has_many, association_options: { target_id: 2, inverse_of: :author, order_by: 'position_in_author' }) }
- let(:type) { build_content_type('Authors', label_field_name: :name, associations: [field]) }
+ let(:type) { build_content_type('Authors', label_field_name: :name, association_fields: [field]) }
let(:entries) { [{ content_type_id: 1, _id: 'john-doe', name: 'John Doe' }] }
let(:other_type) { build_content_type('Articles', _id: 2, label_field_name: :title, fields_by_name: { name: instance_double('Field', name: :title, type: :string) }) }
let(:other_entries) {
@@ @@ -302,7 +302,7 @@ describe Locomotive::Steam::ContentEntryRepository do
describe 'many_to_many' do
let(:field) { instance_double('Field', name: :articles, type: :many_to_many, association_options: { target_id: 2, inverse_of: :authors }) }
- let(:type) { build_content_type('Authors', label_field_name: :name, associations: [field]) }
+ let(:type) { build_content_type('Authors', label_field_name: :name, association_fields: [field]) }
let(:entries) { [{ content_type_id: 1, _id: 1, name: 'John Doe', article_ids: ['hello-world', 'lorem-ipsum'] }] }
let(:other_type) { build_content_type('Articles', _id: 2, label_field_name: :title, fields_by_name: { name: instance_double('Field', name: :title, type: :string) }) }
let(:other_entries) {
@@ @@ -339,7 +339,7 @@ describe Locomotive::Steam::ContentEntryRepository do
slug: name.to_s.downcase,
order_by: nil,
localized_names: [],
- associations: [],
+ association_fields: [],
fields_by_name: {}
}.merge(attributes))
end