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 | |