2017-11-27 23 views
2

私は最近、同じ結果を得る2つの方法の選択のジレンマに直面したプロジェクトに取り組んでいました。クラス構造は次のとおりです。Ruby on railsアクティブなレコードのクエリ

class Book < ApplicationRecord 
    belongs_to :author 
end 

class Author < ApplicationRecord 
    has_many :books 
end 

著者には、姓、名字があります。ある書籍の著者名をインスタンスメソッドとして取得したいと考えています。シンプルなアクティブレコードに関しては

、本は著者に関連付けされているので、以下のように、私たちは本のために著者名を取得することができますが:

例えばBookクラスでは、我々は持っている:

class Book < ApplicationRecord 
    belongs_to :author 

    def author_name 
    "#{author.first_name} #{author.last_name}" 
    end 

end 

をと私たちは結果を得る!

しかし、依存性を最小限に抑えるという目標(POODR Book)、将来の変更の容易さ、よりオブジェクト指向の設計によれば、本は著者のプロパティを知るべきではありません。それはインターフェイスによって作成者オブジェクトと対話する必要があります。

だから、著者名を取得する責任者でないようにしてください。著者クラスはすべきです。

class Book < ApplicationRecord 
    belongs_to :author 

    def author_name 
    get_author_name(self.author_id) 
    end 

    private 

    #minimizing class dependecies by providing private methods as external interfaces 
    def get_author_name(author_id) 
    Author.get_author_name_from_id(author_id) 
    end 

end 

class Author < ApplicationRecord 
    has_many :books 

    #class methods which provides a gate-way for other classes to communicate through interfaces, thus reducing coupling. 

    def self.get_author_name_from_id(id) 
     author = self.find_by_id(id) 
     author == nil ? "Author Record Not Found" : "#{author.first_name.titleize} #{author.last_name.titleize}" 
    end 

end 

さて、この本はちょうど著者と著者が提供するパブリックインターフェースは確かに優れたデザインです。その性質からフルネームを取得する責任を処理していると相互作用しています。

私は私のコンソールに2つの別々のメソッドとしてのクエリを実行してみました:

class Book < ApplicationRecord 
    def author_name 
    get_author_name(self.author_id) 
    end 

    def author_name2 
    "#{author.last_name} + #{author.first_name}" 
    end 
end 

結果を以下に示す:両方が同じクエリを実行するように enter image description here

が見えます。

私の質問があり

  1. はAuthor.find_by_id(AUTHOR_ID)は Authorクラス内部で呼び出さ.last_nameとしてレールがブックからのメッセージパッシングを通じて( に同じSQLクエリをBookクラスの内側と呼ばれるauthor.last_nameを変換していクラス)のデータサイズが大きい場合は?
  2. データサイズが大きい場合、どちらの方がパフォーマンスが良いですか?
  3. Bookクラスのauthor.last_nameを呼び出すと、デザイン の原則に違反していませんか?

答えて

4

。パフォーマンスに関しては

class Book < ApplicationRecord 
    belongs_to :author 
    delegate :name, to: :author, prefix: true, allow_nil: true 
end 

class Author < ApplicationRecord 
    has_many :books 
    def name 
    "#{first_name.titleize} #(last_name.titleize}" 
    end 
end 

あなたが本クエリの時点で著者をjoin場合は、単一のクエリを実行してしまいます。

@books = Book.joins(:author) 

あなたは@booksを反復処理し、あなたが何のSQLクエリがauthorsテーブルに行われる必要がありませんbook.author_name個別に呼び出します。

+1

ここで最高の答え - きれいで、きちんとした、自明の説明。 'prefix'と' allow_nil'オプションはこのシナリオに特に適しています。 (あなたはちょっとタイプミスがあると思っています - それは 'join '複数ではないでしょうか?) – SRack

+0

@SRackよく見つかった!今修正されました。 – SteveTurczyn

1

1)明らかに、書籍& authorsテーブルのJOINを実行しません。あなたが作ったのは、1つのjoinの代わりに2つのクエリが必要です。book.find(id)author.find(book.author_id)があります。

2)JOINの方が速いはずです。

3)last_nameはパブリックインターフェイスなので、デザイン原則には絶対に違反しません。外部からの著者の苗字にアクセスする場合、それは原則に違反します:Book.find(1).author.last_name - それは悪いことです。正解:Book.find(1).authors_last_name - モデルクラス内の著者名にアクセスします。

あなたの提供されている例は私にとっては複雑すぎるようです。

あなたが共有した例によると、あなたはその本の著者の完全な名前を取得したいだけです。だから、分割責任の考え方は正しいのですが、Authorクラスのようなインスタンス方法full_name、シンプルでなければなりません:

class Author < ApplicationRecord 
    has_many :books 

    def full_name 
    "#{author.first_name.titleize} #{author.last_name.titleize}" 
    end 
end 

class Book < ActiveRecord::Base 
    belongs_to :author 

    def author_name 
    author.full_name 
    end 
end 

注、このコードでは直接クエリがそこにいるん。 (ビュー、apiレスポンスなどで)著者の名前がどこかで必要になると、Railsは最適化されたクエリを可能にします(ただし、ユースケースにもよりますが、書籍の繰り返しを呼び出すと効果がない可能性があります) authorをループで呼び出す)

+1

IMO、 'Book'クラスの' author_name'メソッドは、authorクラス 'full_name'メソッドに委譲するので不要です。この情報を取得するコードは、作成者クラス自体から直接取得できます。したがって、カップリングを減らす – xeon131

1

私の経験では、コードの複雑さを最小限に抑え、スケーラビリティの問題を最小限にすることのバランスをとっています。

ただし、この場合には、私はクラスの懸念を分離し、コードを最小限にする最も簡単な解決策は、単に使用するのだろうと思う:@book.author.full_name

そして、あなたのAuthor.rb Author.rbでFULL_NAMEを定義するには:

def full_name 
    "#{self.first_name self.last_name}" 
end 

これにより、コードが大幅に単純化されます。たとえば、将来、AuthorがあるMagazineという別のモデルがある場合は、Magazineモデルでもauthor_nameを定義する必要はありません。単に@magazine.author.full_nameを使用します。これはあなたのコードをきれいに乾かします。

1

私はfull_nameが本ではなく著者の所有物であるため、2番目の方法が好まれます。書籍にその情報にアクセスする場合は、book.author&.full_name&は著者のない書籍のケースを扱うためのものです)を使用できます。

が、以下のように私はリファクタリングを示唆している:それは委任を使用するために、実際にはるかに一般的とsimplierだ

class Book < ApplicationRecord 
    belongs_to :author 
end 

class Author < ApplicationRecord 
    has_many :books 

    def full_name 
    "#{firstname} #{lastname}" 
    end 
end 
1
  1. は、レールは、大きなデータサイズの場合Author.find_by_idと同じSQLクエリ(Bookクラスからのメッセージパッシングを介して)Authorクラスの内側と呼ばれる(AUTHOR_ID).last_nameにBookクラス内部で呼び出さauthor.last_nameを変換していますか?

あなたの例のように、両方とも同じクエリを生成します。しかし、Book/Authorを取得しているときにinclude \ join節があると、両方とも異なるクエリが生成されます。

レール慣習に従って、Author.find_by_id(author_id)。last_nameは、メソッドが呼び出されるたびに常にデータベースのクエリを起動するため、お勧めしません。メモリからオブジェクトを識別するスマートな、またはメモリにない場合はデータベースからフェッチする関連オブジェクトのメソッドを呼び出すために、レールの関連付けインタフェースを使用する必要があります。

  1. データサイズが大きい場合は、どちらの方がパフォーマンスが良いですか?

    author.last_nameは、ジョイン、インクルード、およびメモ句を使用する場合はそれを処理し、N + 1クエリの問題を回避するため、より優れています。

  2. Bookクラスのauthor.last_nameを呼び出してもデザイン原則に違反していませんか?

    いいえでも@Steve Suggestedのような代理人を使用することもできます。

関連する問題