Permalink
Browse files

Support for the Many-to-One association (#398)

* Starting to fully support the many-to-one association

- belongs_to will now instantiate a
Hanami::Associations::BelongsToAssociation
- The relation name will be pluralized internally

* Rubocop warnings. Repo#assoc now works as intended

* Changes #deep_symbolize! for #symbolize!

Start to implement the BelongsTo#create method.

* Unforeseen break due to the deep_symbolize change

* Removes the child-parent creation test and fixture

* Revert my changes to `schemaless_test` and implement a fix for the broken test.

 Expands a bit the belongs_to tests.

* Cleanup
  • Loading branch information...
mereghost authored and jodosha committed May 22, 2017
1 parent e8554ca commit 7d3f059e76ae87a5582ebc75211cbc57da16ce83
@@ -5,14 +5,95 @@ module Model
module Associations
# Many-To-One association
#
# @since 0.7.0
# @since x.x.x
# @api private
class BelongsTo
# @since 0.7.0
# @since x.x.x
# @api private
def self.schema_type(entity)
Sql::Types::Schema::AssociationType.new(entity)
end

# @since x.x.x
# @api private
attr_reader :repository

# @since x.x.x
# @api private
attr_reader :source

# @since x.x.x
# @api private
attr_reader :target

# @since x.x.x
# @api private
attr_reader :subject

# @since x.x.x
# @api private
attr_reader :scope

# @since x.x.x
# @api private
def initialize(repository, source, target, subject, scope = nil)
@repository = repository
@source = source
@target = target
@subject = subject.to_hash unless subject.nil?
@scope = scope || _build_scope
freeze
end

# @since x.x.x
# @api private
def one
scope.one
end

private

# @since x.x.x
# @api private
def container
repository.container
end

# @since x.x.x
# @api private
def primary_key
association_keys.first
end

# @since x.x.x
# @api private
def relation(name)
repository.relations[Hanami::Utils::String.new(name).pluralize]
end

# @since x.x.x
# @api private
def foreign_key
association_keys.last
end

# Returns primary key and foreign key
#
# @since x.x.x
# @api private
def association_keys
relation(source)
.associations[target]
.__send__(:join_key_map, container.relations)
end

# @since x.x.x
# @api private
def _build_scope
result = relation(target)
result = result.where(foreign_key => subject.fetch(primary_key)) unless subject.nil?
result.as(Model::MappedRelation.mapper_name)
end
end
end
end
@@ -19,9 +19,10 @@ def has_many(relation, *)
@repository.__send__(:relations, relation)
end

# @since 0.7.0
# @since x.x.x
# @api private
def belongs_to(*)
def belongs_to(relation, *)
@repository.__send__(:relations, Hanami::Utils::String.new(relation).pluralize.to_sym)
end
end
end
@@ -60,7 +60,7 @@ def initialize(message = 'Constraint has been violated')
# Error for Unique Constraint Violation
#
# @since 0.6.1
class UniqueConstraintViolationError < Error
class UniqueConstraintViolationError < ConstraintViolationError
# @since 0.6.1
# @api private
def initialize(message = 'Unique constraint has been violated')
@@ -71,7 +71,7 @@ def initialize(message = 'Unique constraint has been violated')
# Error for Foreign Key Constraint Violation
#
# @since 0.6.1
class ForeignKeyConstraintViolationError < Error
class ForeignKeyConstraintViolationError < ConstraintViolationError
# @since 0.6.1
# @api private
def initialize(message = 'Foreign key constraint has been violated')
@@ -82,7 +82,7 @@ def initialize(message = 'Foreign key constraint has been violated')
# Error for Not Null Constraint Violation
#
# @since 0.6.1
class NotNullConstraintViolationError < Error
class NotNullConstraintViolationError < ConstraintViolationError
# @since 0.6.1
# @api private
def initialize(message = 'NOT NULL constraint has been violated')
@@ -93,7 +93,7 @@ def initialize(message = 'NOT NULL constraint has been violated')
# Error for Check Constraint Violation raised by Sequel
#
# @since 0.6.1
class CheckConstraintViolationError < Error
class CheckConstraintViolationError < ConstraintViolationError
# @since 0.6.1
# @api private
def initialize(message = 'Check constraint has been violated')
@@ -0,0 +1,28 @@
RSpec.describe 'Associations (belongs_To)' do
it "returns nil if association wasn't preloaded" do
repository = BookRepository.new
book = repository.create(name: 'L')
found = repository.find(book.id)

expect(found.author).to be(nil)
end

it 'preloads the associated record' do
repository = BookRepository.new
author = AuthorRepository.new.create(name: 'Michel Foucault')
book = repository.create(author_id: author.id, title: 'Surveiller et punir')
found = repository.find_with_author(book.id)

expect(found).to eq(book)
expect(found.author).to eq(author)
end

it 'returns an author' do
repository = BookRepository.new
author = AuthorRepository.new.create(name: 'Maurice Leblanc')
book = repository.create(author_id: author.id, title: "L'Aguille Creuse")
found = repository.author_for(book)

expect(found).to eq(author)
end
end
@@ -138,6 +138,14 @@ class BookRepository < Hanami::Repository
associations do
belongs_to :author
end

def find_with_author(id)
aggregate(:author).where(id: id).as(Book).one
end

def author_for(book)
assoc(:author, book).one
end
end

class OperatorRepository < Hanami::Repository
@@ -0,0 +1,13 @@
RSpec.describe Hanami::Model::CheckConstraintViolationError do
it "inherits from Hanami::Model::ConstraintViolationError" do
expect(described_class.ancestors).to include(Hanami::Model::ConstraintViolationError)
end

it "has a default error message" do
expect { raise described_class }.to raise_error(described_class, "Check constraint has been violated")
end

it "allows custom error message" do
expect { raise described_class.new("Ouch") }.to raise_error(described_class, "Ouch")
end
end
@@ -0,0 +1,13 @@
RSpec.describe Hanami::Model::ConstraintViolationError do
it "inherits from Hanami::Model::Error" do
expect(described_class.ancestors).to include(Hanami::Model::Error)
end

it "has a default error message" do
expect { raise described_class }.to raise_error(described_class, "Constraint has been violated")
end

it "allows custom error message" do
expect { raise described_class.new("Ouch") }.to raise_error(described_class, "Ouch")
end
end
@@ -0,0 +1,5 @@
RSpec.describe Hanami::Model::Error do
it "inherits from StandardError" do
expect(described_class.ancestors).to include(StandardError)
end
end
@@ -0,0 +1,13 @@
RSpec.describe Hanami::Model::ForeignKeyConstraintViolationError do
it "inherits from Hanami::Model::ConstraintViolationError" do
expect(described_class.ancestors).to include(Hanami::Model::ConstraintViolationError)
end

it "has a default error message" do
expect { raise described_class }.to raise_error(described_class, "Foreign key constraint has been violated")
end

it "allows custom error message" do
expect { raise described_class.new("Ouch") }.to raise_error(described_class, "Ouch")
end
end
@@ -0,0 +1,13 @@
RSpec.describe Hanami::Model::NotNullConstraintViolationError do
it "inherits from Hanami::Model::ConstraintViolationError" do
expect(described_class.ancestors).to include(Hanami::Model::ConstraintViolationError)
end

it "has a default error message" do
expect { raise described_class }.to raise_error(described_class, "NOT NULL constraint has been violated")
end

it "allows custom error message" do
expect { raise described_class.new("Ouch") }.to raise_error(described_class, "Ouch")
end
end
@@ -0,0 +1,13 @@
RSpec.describe Hanami::Model::UniqueConstraintViolationError do
it "inherits from Hanami::Model::ConstraintViolationError" do
expect(described_class.ancestors).to include(Hanami::Model::ConstraintViolationError)
end

it "has a default error message" do
expect { raise described_class }.to raise_error(described_class, "Unique constraint has been violated")
end

it "allows custom error message" do
expect { raise described_class.new("Ouch") }.to raise_error(described_class, "Ouch")
end
end

0 comments on commit 7d3f059

Please sign in to comment.