many_to_many association for content entries

did committed Mar 04, 2015
commit bb1deb58e3143dd2c704e0becc48902cca3f00c4
Showing 9 changed files with 90 additions and 56 deletions
locomotive/steam/adapters/filesystem/yaml_loaders/content_entry.rb b/lib/locomotive/steam/adapters/filesystem/yaml_loaders/content_entry.rb +21 -8
@@ @@ -20,23 +20,36 @@ module Locomotive
each(content_type_slug) do |label, attributes, position|
_attributes = { _position: position, _label: label.to_s }.merge(attributes)
- setup_belongs_to_associations(_attributes)
+ setup_associations(_attributes)
list << _attributes
end
end
end
- def setup_belongs_to_associations(attributes)
- content_type.belongs_to_fields.each do |field|
- # <name>_id
- attributes[:"#{field.name}_id"] = attributes.delete(field.name.to_sym)
-
- # _position_in_<name>
- attributes[:"_position_in_#{field.name}"] = attributes[:_position]
+ def setup_associations(attributes)
+ content_type.association_fields.each do |field|
+ case field.type
+ when :belongs_to
+ setup_belongs_to_association(field, attributes)
+ when :many_to_many
+ setup_many_to_many_association(field, attributes)
+ end
end
end
+ def setup_belongs_to_association(field, attributes)
+ # <name>_id
+ attributes[:"#{field.name}_id"] = attributes.delete(field.name.to_sym)
+
+ # _position_in_<name>
+ attributes[:"_position_in_#{field.name}"] = attributes[:_position]
+ end
+
+ def setup_many_to_many_association(field, attributes)
+ attributes[:"#{field.name.to_s.singularize}_ids"] = attributes.delete(field.name.to_sym)
+ end
+
def each(slug, &block)
position = 0
_load(File.join(path, "#{slug}.yml")).each do |element|
locomotive/steam/models.rb b/lib/locomotive/steam/models.rb +1 -0
@@ @@ -4,6 +4,7 @@ require_relative 'models/associations/embedded'
require_relative 'models/associations/referenced'
require_relative 'models/associations/belongs_to'
require_relative 'models/associations/has_many'
+ require_relative 'models/associations/many_to_many'
require_relative 'models/entity'
require_relative 'models/mapper'
require_relative 'models/scope'
locomotive/steam/models/associations/has_many.rb b/lib/locomotive/steam/models/associations/has_many.rb +2 -1
@@ @@ -6,7 +6,8 @@ module Locomotive::Steam
def __load__
# Note: in adapters like the FileSystem one, we use slugs
- # to reference other entities in associations.
+ # to reference other entities in associations,
+ # that is why we call identifier_name.
id = @repository.i18n_value_of(@entity, @repository.identifier_name)
key = :"#{@options[:inverse_of]}_id"
locomotive/steam/models/associations/many_to_many.rb b/lib/locomotive/steam/models/associations/many_to_many.rb +26 -0
@@ @@ -0,0 +1,26 @@
+ module Locomotive::Steam
+ module Models
+
+ # Note: represents an embedded collection
+ class ManyToManyAssociation < ReferencedAssociation
+
+ def __load__
+ # Note: in adapters like the FileSystem one, we use slugs
+ # to reference other entities in associations,
+ # that is why we call identifier_name.
+ source_key = :"#{@options[:association_name].to_s.singularize}_ids"
+ key = @repository.k(@repository.identifier_name, :in)
+
+ @repository.local_conditions[key] = @entity[source_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?
+
+ # all the further calls (method_missing) will be delegated to @repository
+ @repository
+ end
+
+ end
+
+ end
+ end
locomotive/steam/models/mapper.rb b/lib/locomotive/steam/models/mapper.rb +2 -1
@@ @@ -6,7 +6,8 @@ module Locomotive::Steam
ASSOCIATION_CLASSES = {
embedded: EmbeddedAssociation,
belongs_to: BelongsToAssociation,
- has_many: HasManyAssociation
+ has_many: HasManyAssociation,
+ many_to_many: ManyToManyAssociation
}.freeze
attr_reader :name, :options, :default_attributes, :localized_attributes, :associations
locomotive/steam/repositories/content_entry_repository.rb b/lib/locomotive/steam/repositories/content_entry_repository.rb +0 -33
@@ @@ -129,39 +129,6 @@ module Locomotive
first { where(conditions) }
end
- # def association(metadata, conditions = {})
- # case metadata.type
- # when :belongs_to then belongs_to_association(metadata)
- # when :has_many then has_many_association(metadata, conditions)
- # when :many_to_many then many_to_many_association(metadata, conditions)
- # end
- # end
-
- # def belongs_to_association(metadata)
- # type = type_from(metadata.target_class_slug)
- # by_slug(type, metadata.target_slugs.first)
- # end
-
- # def has_many_association(metadata, conditions)
- # many_association(metadata,
- # { metadata.target_field => localized_slug(metadata.source) }.merge(conditions))
- # end
-
- # def many_to_many_association(metadata, conditions)
- # many_association(metadata,
- # { '_slug.in' => metadata.target_slugs }.merge(conditions))
- # end
-
- # def many_association(metadata, conditions)
- # type = type_from(metadata.target_class_slug)
-
- # if order_by = metadata.order_by
- # conditions = { order_by: order_by }.merge(conditions)
- # end
-
- # all(type, conditions)
- # end
-
end
end
locomotive/steam/repositories/content_type_field_repository.rb b/lib/locomotive/steam/repositories/content_type_field_repository.rb +0 -8
@@ @@ -29,14 +29,6 @@ module Locomotive
query { where(required: true) }.all
end
- # def belongs_to
- # query { where(type: :belongs_to) }.all
- # end
-
- # def has_many
- # query { where(type: :has_many) }.all
- # end
-
def localized_names
query { where(localized: true) }.all.map(&:name)
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', slug: 'bands', belongs_to_fields: []) }
+ let(:content_type) { instance_double('Bands', slug: 'bands', association_fields: []) }
let(:scope) { instance_double('Scope', locale: :en, context: { content_type: content_type }) }
let(:loader) { described_class.new(site_path) }
@@ @@ -22,8 +22,8 @@ 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') }
- let(:content_type) { instance_double('Songs', slug: 'songs', belongs_to_fields: [field]) }
+ let(:field) { instance_double('Field', name: 'band', type: :belongs_to) }
+ let(:content_type) { instance_double('Songs', slug: 'songs', association_fields: [field]) }
it 'adds a new attribute for the foreign key' do
expect(subject.first[:band_id]).to eq 'pearl-jam'
spec/unit/repositories/content_entry_repository_spec.rb +35 -2
@@ @@ -198,7 +198,7 @@ describe Locomotive::Steam::ContentEntryRepository do
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, _slug: 'john-doe', name: 'John Doe' }] }
- let(:type_repository) { instance_double('ContentTypeRepository', belongs_to: [field]) }
+ let(:type_repository) { instance_double('ContentTypeRepository') }
before do
allow(type).to receive(:fields).and_return(type_repository)
@@ @@ -231,7 +231,7 @@ describe Locomotive::Steam::ContentEntryRepository do
]
}
- let(:type_repository) { instance_double('ContentTypeRepository', has_many: [field]) }
+ let(:type_repository) { instance_double('ContentTypeRepository') }
before do
allow(type).to receive(:fields).and_return(type_repository)
@@ @@ -250,6 +250,39 @@ describe Locomotive::Steam::ContentEntryRepository do
end
+ 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, 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) {
+ [
+ { content_type_id: 2, _slug: 'hello-world', title: 'Hello world', author_id: 'john-doe', position_in_author: 2 },
+ { content_type_id: 2, _slug: 'lorem-ipsum', title: 'Lorem ipsum', author_id: 'john-doe', position_in_author: 1 },
+ { content_type_id: 2, _slug: 'lost', title: 'Lost', author_id: 'jane-doe' },
+ ]
+ }
+
+ let(:type_repository) { instance_double('ContentTypeRepository') }
+
+ before do
+ allow(type).to receive(:fields).and_return(type_repository)
+ allow(content_type_repository).to receive(:find).with(2).and_return(other_type)
+ end
+
+ subject { repository.with(type).by_slug('john-doe') }
+
+ it { expect(subject.articles.class).to eq Locomotive::Steam::Models::ManyToManyAssociation }
+
+ it 'calls the new repository to fetch the target entities' do
+ articles = subject.articles
+ allow(adapter).to receive(:collection).and_return(other_entries)
+ expect(articles.all.map(&:title)).to eq ['Hello world', 'Lorem ipsum']
+ end
+
+ end
+
def build_content_type(name, attributes = {})
instance_double(name,
{