Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

ActiveRecord 대 Ecto2부

이것은 "ActiveRecord vs. Ecto" 시리즈의 두 번째 부분으로, 배트맨과 Batgirl이 데이터베이스 쿼리를 놓고 싸우고 사과와 오렌지를 비교합니다.

ActiveRecord 대 Ecto 파트 1에서 데이터베이스 스키마와 마이그레이션을 살펴본 후 이 게시물에서는 ActiveRecord와 Ecto를 모두 사용하여 개발자가 데이터베이스를 쿼리할 수 있는 방법과 ActiveRecord와 Ecto가 동일한 요구 사항을 처리할 때 비교하는 방법을 다룹니다. 그 과정에서 Batgirl의 1989-2011년 신원도 알게 됩니다.

시드 데이터

시작하자! 이 시리즈의 첫 번째 게시물에서 정의한 데이터베이스 구조를 기반으로 usersinvoices 테이블에는 다음 데이터가 저장되어 있습니다.

사용자

id 이름 이메일 created_at* 업데이트됨
1 베트 케인 bette@kane.test 2018-01-01 10:01:00 2018-01-01 10:01:00
2 바바라 고든 barbara@gordon.test 2018-01-02 10:02:00 2018-01-02 10:02:00
3 카산드라 케인 cassandra@cain.test 2018-01-03 10:03:00 2018-01-03 10:03:00
4 스테파니 브라운 stephanie@brown.test 2018-01-04 10:04:00 2018-01-04 10:04:00

* ActiveRecord의 created_at 필드의 이름은 inserted_at입니다. 기본적으로 Ecto에서.

인보이스

id user_id 결제 수단 유료_at created_at* 업데이트됨
1 1 신용카드 2018-02-01 08:00:00 2018-01-02 08:00:00 2018-01-02 08:00:00
2 2 페이팔 2018-02-01 08:00:00 2018-01-03 08:00:00 2018-01-03 08:00:00
3 3 2018-01-04 08:00:00 2018-01-04 08:00:00
4 4 2018-01-05 08:00:00 2018-01-05 08:00:00

* ActiveRecord의 created_at 필드의 이름은 inserted_at입니다. 기본적으로 Ecto에서.

이 게시물을 통해 수행된 쿼리는 위의 데이터가 데이터베이스에 저장되어 있다고 가정하므로 읽을 때 이 정보를 염두에 두십시오.

기본 키를 사용하여 항목 찾기

기본 키를 사용하여 데이터베이스에서 레코드를 가져오는 것부터 시작하겠습니다.

액티브 레코드

irb(main):001:0> User.find(1)
User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, full_name: "Bette Kane", email: "bette@kane.test", created_at: "2018-01-01 10:01:00", updated_at: "2018-01-01 10:01:00">

엑토

iex(3)> Repo.get(User, 1)
[debug] QUERY OK source="users" db=5.2ms decode=2.5ms queue=0.1ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1]
%Financex.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "bette@kane.test",
  full_name: "Bette Kane",
  id: 1,
  inserted_at: ~N[2018-01-01 10:01:00.000000],
  invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
  updated_at: ~N[2018-01-01 10:01:00.000000]
}

비교

두 경우 모두 상당히 유사합니다. ActiveRecord는 find에 의존합니다. User의 클래스 메소드 모델 클래스. 모든 ActiveRecord 자식 클래스에는 고유한 find가 있습니다. 방법입니다.

Ecto는 매핑 계층과 도메인 간의 중재자로서 Repository 개념에 의존하는 다른 접근 방식을 사용합니다. Ecto를 사용할 때 User 모듈 자체를 찾는 방법에 대한 지식이 없습니다. 이러한 책임은 Repo에 있습니다. 모듈을 사용하여 아래 데이터 저장소(이 경우 Postgres)에 매핑할 수 있습니다.

SQL 쿼리 자체를 비교할 때 몇 가지 차이점을 발견할 수 있습니다.

  • ActiveRecord는 모든 필드(users.* ), Ecto는 schema에 나열된 필드만 로드합니다. 정의.
  • ActiveRecord에는 LIMIT 1이(가) 포함됩니다. Ecto는 그렇지 않습니다.

모든 항목 가져오기

한 단계 더 나아가 데이터베이스에서 모든 사용자를 로드해 보겠습니다.

액티브 레코드

irb(main):001:0> User.all
User Load (0.5ms)  SELECT  "users".* FROM "users" LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<User id: 1, full_name: "Bette Kane", email: "bette@kane.test", created_at: "2018-01-01 10:01:00", updated_at: "2018-01-01 10:01:00">, #<User id: 2, full_name: "Barbara Gordon", email: "barbara@gordon.test", created_at: "2018-01-02 10:02:00", updated_at: "2018-01-02 10:02:00">, #<User id: 3, full_name: "Cassandra Cain", email: "cassandra@cain.test", created_at: "2018-01-03 10:03:00", updated_at: "2018-01-03 10:03:00">, #<User id: 4, full_name: "Stephanie Brown", email: "stephanie@brown.test", created_at: "2018-01-04 10:04:00", updated_at: "2018-01-04 10:04:00">]>

엑토

iex(4)> Repo.all(User)
[debug] QUERY OK source="users" db=2.8ms decode=0.2ms queue=0.2ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 []
[
  %Financex.Accounts.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "bette@kane.test",
    full_name: "Bette Kane",
    id: 1,
    inserted_at: ~N[2018-01-01 10:01:00.000000],
    invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
    updated_at: ~N[2018-01-01 10:01:00.000000]
  },
  %Financex.Accounts.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "barbara@gordon.test",
    full_name: "Barbara Gordon",
    id: 2,
    inserted_at: ~N[2018-01-02 10:02:00.000000],
    invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
    updated_at: ~N[2018-01-02 10:02:00.000000]
  },
  %Financex.Accounts.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "cassandra@cain.test",
    full_name: "Cassandra Cain",
    id: 3,
    inserted_at: ~N[2018-01-03 10:03:00.000000],
    invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
    updated_at: ~N[2018-01-03 10:03:00.000000]
  },
  %Financex.Accounts.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "stephanie@brown.test",
    full_name: "Stephanie Brown",
    id: 4,
    inserted_at: ~N[2018-01-04 10:04:00.000000],
    invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
    updated_at: ~N[2018-01-04 10:04:00.000000]
  }
]

비교

이전 섹션과 똑같은 패턴을 따릅니다. ActiveRecord는 all class 메소드와 Ecto는 리포지토리 패턴에 의존하여 레코드를 로드합니다.

SQL 쿼리에는 다음과 같은 몇 가지 차이점이 있습니다.

  • 이전 섹션과 마찬가지로 ActiveRecord는 모든 필드(users.* ), Ecto는 schema에 나열된 필드만 로드합니다. 정의.
  • ActiveRecord는 LIMIT 11도 정의합니다. , Ecto는 단순히 모든 것을 로드합니다. 이 제한은 inspect에서 비롯됩니다. 콘솔에서 사용된 메서드(https://github.com/rails/rails/blob/master/activerecord/lib/active_record/relation.rb#L599).

조건 쿼리

테이블에서 모든 레코드를 가져와야 하는 경우는 거의 없습니다. 일반적으로 필요한 것은 조건을 사용하여 반환된 데이터를 필터링하는 것입니다.

이 예를 사용하여 모든 invoices를 나열해 보겠습니다. 아직 지불해야 하는 항목(WHERE paid_at IS NULL ).

액티브 레코드

irb(main):024:0> Invoice.where(paid_at: nil)
Invoice Load (18.2ms)  SELECT  "invoices".* FROM "invoices" WHERE "invoices"."paid_at" IS NULL LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 3, user_id: 3, payment_method: nil, paid_at: nil, created_at: "2018-01-04 08:00:00", updated_at: "2018-01-04 08:00:00">, #<Invoice id: 4, user_id: 4, payment_method: nil, paid_at: nil, created_at: "2018-01-05 08:00:00", updated_at: "2018-01-05 08:00:00">]>

엑토

iex(19)> where(Invoice, [i], is_nil(i.paid_at)) |> Repo.all()
[debug] QUERY OK source="invoices" db=20.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 WHERE (i0."paid_at" IS NULL) []
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 3,
    inserted_at: ~N[2018-01-04 08:00:00.000000],
    paid_at: nil,
    payment_method: nil,
    updated_at: ~N[2018-01-04 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 3
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 4,
    inserted_at: ~N[2018-01-04 08:00:00.000000],
    paid_at: nil,
    payment_method: nil,
    updated_at: ~N[2018-01-04 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 4
  }
]

비교

두 예에서 where 키워드가 사용되며 이는 SQL WHERE에 대한 연결입니다. 절. 생성된 SQL 쿼리는 매우 유사하지만 두 도구가 거기에 도달하는 방식에는 몇 가지 중요한 차이점이 있습니다.

ActiveRecord는 paid_at: nil을 변환합니다. paid_at IS NULL에 대한 인수 자동으로 SQL 문. Ecto를 사용하여 동일한 출력을 얻으려면 개발자는 is_nil()을 호출하여 의도에 대해 더 명시적이어야 합니다. .

강조해야 할 또 다른 차이점은 where 함수의 "순수한" 동작입니다. 엑토에서. where를 호출할 때 기능만 수행하면 데이터베이스와 상호 작용하지 않습니다. where 반환 함수는 Ecto.Query입니다. 구조체:

iex(20)> where(Invoice, [i], is_nil(i.paid_at))
#Ecto.Query<from i in Financex.Accounts.Invoice, where: is_nil(i.paid_at)>

데이터베이스는 Repo.all()일 때만 터치됩니다. 함수가 호출되어 Ecto.Query를 전달합니다. 구조체를 인수로 사용합니다. 이 접근 방식은 다음 섹션의 주제인 Ecto에서 쿼리 구성을 허용합니다.

쿼리 구성

데이터베이스 쿼리의 가장 강력한 측면 중 하나는 구성입니다. 하나 이상의 조건을 포함하는 방식으로 쿼리를 설명합니다.

원시 SQL 쿼리를 작성하는 경우 일종의 연결을 사용할 수 있음을 의미합니다. 두 가지 조건이 있다고 상상해 보세요.

  1. not_paid = 'paid_at IS NOT NULL'
  2. paid_with_paypal = 'payment_method = "Paypal"'

원시 SQL을 사용하여 이 두 조건을 결합하려면 다음과 유사한 것을 사용하여 결합해야 함을 의미합니다.

SELECT * FROM invoices WHERE #{not_paid} AND #{paid_with_paypal}

다행히 ActiveRecord와 Ecto 모두 이에 대한 솔루션을 가지고 있습니다.

액티브 레코드

irb(main):003:0> Invoice.where.not(paid_at: nil).where(payment_method: "Paypal")
Invoice Load (8.0ms)  SELECT  "invoices".* FROM "invoices" WHERE "invoices"."paid_at" IS NOT NULL AND "invoices"."payment_method" = $1 LIMIT $2  [["payment_method", "Paypal"], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">]>

엑토

iex(6)> Invoice |> where([i], not is_nil(i.paid_at)) |> where([i], i.payment_method == "Paypal") |> Repo.all()
[debug] QUERY OK source="invoices" db=30.0ms decode=0.6ms queue=0.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 WHERE (NOT (i0."paid_at" IS NULL)) AND (i0."payment_method" = 'Paypal') []
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 2,
    inserted_at: ~N[2018-01-03 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~N[2018-01-03 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  }
]

비교

두 쿼리 모두 "Paypal에서 어떤 인보이스를 지불하고 사용했습니까?"라는 동일한 질문에 답하고 있습니다.

이미 예상한 대로 ActiveRecord는 쿼리를 작성하는 보다 간결한 방법(예:예)을 제공하는 반면 Ecto는 개발자가 쿼리 작성에 더 많은 비용을 지출해야 합니다. 평소와 같이 Batgirl(고아, Cassandra Cain 정체성을 가진 음소거) 또는 Activerecord는 장황하지 않습니다.

위에 표시된 Ecto 쿼리의 장황함과 명백한 복잡성에 속지 마십시오. 실제 환경에서 해당 쿼리는 다음과 같이 다시 작성됩니다.

Invoice
|> where([i], not is_nil(i.paid_at))
|> where([i], i.payment_method == "Paypal")
|> Repo.all()

그 각도에서 볼 때 where 함수의 "순수한" 측면의 조합은 , 자체적으로 데이터베이스 작업을 수행하지 않는 파이프 연산자는 Ecto의 쿼리 구성을 정말 깔끔하게 만듭니다.

주문

주문은 쿼리의 중요한 측면입니다. 이를 통해 개발자는 주어진 쿼리 결과가 지정된 순서를 따르도록 할 수 있습니다.

액티브 레코드

irb(main):002:0> Invoice.order(created_at: :desc)
Invoice Load (1.5ms)  SELECT  "invoices".* FROM "invoices" ORDER BY "invoices"."created_at" DESC LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Invoice id: 4, user_id: 4, payment_method: nil, paid_at: nil, created_at: "2018-01-05 08:00:00", updated_at: "2018-01-05 08:00:00">, #<Invoice id: 3, user_id: 3, payment_method: nil, paid_at: nil, created_at: "2018-01-04 08:00:00", updated_at: "2018-01-04 08:00:00">, #<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">, #<Invoice id: 1, user_id: 1, payment_method: "Credit Card", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-02 08:00:00", updated_at: "2018-01-02 08:00:00">]>

엑토

iex(6)> order_by(Invoice, desc: :inserted_at) |> Repo.all()
[debug] QUERY OK source="invoices" db=19.8ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 ORDER BY i0."inserted_at" DESC []
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 3,
    inserted_at: ~N[2018-01-04 08:00:00.000000],
    paid_at: nil,
    payment_method: nil,
    updated_at: ~N[2018-01-04 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 3
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 4,
    inserted_at: ~N[2018-01-04 08:00:00.000000],
    paid_at: nil,
    payment_method: nil,
    updated_at: ~N[2018-01-04 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 4
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 2,
    inserted_at: ~N[2018-01-03 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~N[2018-01-03 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 1,
    inserted_at: ~N[2018-01-02 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Credit Card",
    updated_at: ~N[2018-01-02 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  }
]

비교

쿼리에 순서를 추가하는 것은 두 도구 모두에서 간단합니다.

Ecto 예제는 Invoice를 사용하지만 첫 번째 매개변수로 order_by 함수는 Ecto.Query도 허용합니다. order_by를 활성화하는 구조체 다음과 같은 컴포지션에서 사용할 함수:

Invoice
|> where([i], not is_nil(i.paid_at))
|> where([i], i.payment_method == "Paypal")
|> order_by(desc: :inserted_at)
|> Repo.all()

제한

제한이 없는 데이터베이스는 무엇입니까? 재해. 다행히 ActiveRecord와 Ecto는 모두 반환된 레코드 수를 제한하는 데 도움이 됩니다.

액티브 레코드

irb(main):004:0> Invoice.limit(2)
Invoice Load (0.2ms)  SELECT  "invoices".* FROM "invoices" LIMIT $1  [["LIMIT", 2]]
=> #<ActiveRecord::Relation [#<Invoice id: 1, user_id: 1, payment_method: "Credit Card", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-02 08:00:00", updated_at: "2018-01-02 08:00:00">, #<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">]>

엑토

iex(22)> limit(Invoice, 2) |> Repo.all()
[debug] QUERY OK source="invoices" db=3.6ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at" FROM "invoices" AS i0 LIMIT 2 []
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 1,
    inserted_at: ~N[2018-01-02 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Credit Card",
    updated_at: ~N[2018-01-02 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 1
  },
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 2,
    inserted_at: ~N[2018-01-03 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~N[2018-01-03 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  }
]

비교

ActiveRecord와 Ecto 모두 쿼리에서 반환되는 레코드 수를 제한하는 방법이 있습니다.

Ecto의 limit order_by와 유사하게 작동합니다. , 쿼리 구성에 적합합니다.

협회

ActiveRecord와 Ecto는 연결을 처리하는 방식에 있어 접근 방식이 다릅니다.

액티브 레코드

ActiveRecord에서는 특별한 작업 없이 모델에 정의된 연결을 사용할 수 있습니다. 예를 들면 다음과 같습니다.

irb(main):012:0> user = User.find(2)
User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
=> #<User id: 2, full_name: "Barbara Gordon", email: "barbara@gordon.test", created_at: "2018-01-02 10:02:00", updated_at: "2018-01-02 10:02:00">
irb(main):013:0> user.invoices
Invoice Load (0.4ms)  SELECT  "invoices".* FROM "invoices" WHERE "invoices"."user_id" = $1 LIMIT $2  [["user_id", 2], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">]>

위의 예는 user.invoices를 호출할 때 사용자 인보이스 목록을 얻을 수 있음을 보여줍니다. . 그렇게 할 때 ActiveRecord는 자동으로 데이터베이스를 쿼리하고 사용자와 관련된 송장을 로드했습니다. 이 접근 방식을 사용하면 작업이 더 쉬워지지만 코드를 적게 작성하거나 추가 단계에 대해 걱정해야 한다는 의미에서 여러 사용자를 반복하고 각 사용자에 대한 인보이스를 가져오는 경우 문제가 될 수 있습니다. 이 문제를 "N + 1 문제"라고 합니다.

ActiveRecord에서 "N + 1 문제"에 대해 제안된 수정 사항은 includes를 사용하는 것입니다. 방법:

irb(main):022:0> user = User.includes(:invoices).find(2)
User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
Invoice Load (0.6ms)  SELECT "invoices".* FROM "invoices" WHERE "invoices"."user_id" = $1  [["user_id", 2]]
=> #<User id: 2, full_name: "Barbara Gordon", email: "barbara@gordon.test", created_at: "2018-01-02 10:02:00", updated_at: "2018-01-02 10:02:00">
irb(main):023:0> user.invoices
=> #<ActiveRecord::Associations::CollectionProxy [#<Invoice id: 2, user_id: 2, payment_method: "Paypal", paid_at: "2018-02-01 08:00:00", created_at: "2018-01-03 08:00:00", updated_at: "2018-01-03 08:00:00">]>

이 경우 ActiveRecord는 invoices를 즉시 로드합니다. 사용자를 가져올 때 연결(표시된 두 개의 SQL 쿼리에서 볼 수 있음).

엑토

이미 눈치채셨겠지만 Ecto는 마술이나 암시를 정말 좋아하지 않습니다. 개발자는 의도를 명시해야 합니다.

user.invoices를 사용하는 것과 동일한 접근 방식을 시도해 보겠습니다. 엑토와 함께:

iex(7)> user = Repo.get(User, 2)
[debug] QUERY OK source="users" db=18.3ms decode=0.6ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [2]
%Financex.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "barbara@gordon.test",
  full_name: "Barbara Gordon",
  id: 2,
  inserted_at: ~N[2018-01-02 10:02:00.000000],
  invoices: #Ecto.Association.NotLoaded<association :invoices is not loaded>,
  updated_at: ~N[2018-01-02 10:02:00.000000]
}
iex(8)> user.invoices
#Ecto.Association.NotLoaded<association :invoices is not loaded>

결과는 Ecto.Association.NotLoaded입니다. . 그다지 유용하지 않습니다.

인보이스에 액세스하려면 개발자가 preload를 사용하여 Ecto에 알려야 합니다. 기능:

iex(12)> user = preload(User, :invoices) |> Repo.get(2)
[debug] QUERY OK source="users" db=11.8ms
SELECT u0."id", u0."full_name", u0."email", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [2]
[debug] QUERY OK source="invoices" db=4.2ms
SELECT i0."id", i0."payment_method", i0."paid_at", i0."user_id", i0."inserted_at", i0."updated_at", i0."user_id" FROM "invoices" AS i0 WHERE (i0."user_id" = $1) ORDER BY i0."user_id" [2]
%Financex.Accounts.User{
  __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
  email: "barbara@gordon.test",
  full_name: "Barbara Gordon",
  id: 2,
  inserted_at: ~N[2018-01-02 10:02:00.000000],
  invoices: [
    %Financex.Accounts.Invoice{
      __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
      id: 2,
      inserted_at: ~N[2018-01-03 08:00:00.000000],
      paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
      payment_method: "Paypal",
      updated_at: ~N[2018-01-03 08:00:00.000000],
      user: #Ecto.Association.NotLoaded<association :user is not loaded>,
      user_id: 2
    }
  ],
  updated_at: ~N[2018-01-02 10:02:00.000000]
}
 
iex(15)> user.invoices
[
  %Financex.Accounts.Invoice{
    __meta__: #Ecto.Schema.Metadata<:loaded, "invoices">,
    id: 2,
    inserted_at: ~N[2018-01-03 08:00:00.000000],
    paid_at: #DateTime<2018-02-01 08:00:00.000000Z>,
    payment_method: "Paypal",
    updated_at: ~N[2018-01-03 08:00:00.000000],
    user: #Ecto.Association.NotLoaded<association :user is not loaded>,
    user_id: 2
  }
]

ActiveRecord와 유사하게 includes , 관련 invoices 가져오기와 함께 미리 로드 , user.invoices를 호출할 때 사용할 수 있습니다. .

비교

다시 한 번, ActiveRecord와 Ecto 간의 전투는 알려진 점인 명시성으로 끝납니다. 두 도구 모두 개발자가 연결에 쉽게 액세스할 수 있도록 해주지만 ActiveRecord는 덜 장황하게 만들지만 결과적으로 예기치 않은 동작이 발생할 수 있습니다. Ecto는 WYSIWYG 유형의 접근 방식을 따르며 개발자가 정의한 쿼리에 표시되는 작업만 수행합니다.

Rails는 응용 프로그램의 모든 다른 계층에 대한 캐싱 전략을 사용하고 홍보하는 것으로 잘 알려져 있습니다. 한 가지 예는 "N + 1 문제"에 전적으로 의존하여 캐싱 메커니즘이 마법을 수행하는 "러시아 인형" 캐싱 접근 방식을 사용하는 것입니다.

검증

ActiveRecord에 있는 대부분의 유효성 검사는 Ecto에서도 사용할 수 있습니다. 다음은 일반적인 유효성 검사 목록과 ActiveRecord와 Ecto가 이를 정의하는 방법입니다.

액티브 레코드 엑토
validates :title, presence: true validate_required(changeset, [:title])
validates :email, confirmation: true validate_confirmation(changeset, :email)
validates :email, format: {with: /@/ } validate_format(changeset, :email, ~r/@/)
validates :start, exclusion: {in: %w(a b)} validate_exclusion(changeset, :start, ~w(a b))
validates :start, inclusion: {in: %w(a b)} validate_inclusion(changeset, :start, ~w(a b))
validates :terms_of_service, acceptance: true validate_acceptance(changeset, :terms_of_service)
validates :password, length: {is: 6} validate_length(changeset, :password, is: 6)
validates :age, numericality: {equal_to: 1} validate_number(changeset, :age, equal_to: 1)

마무리

여기 있습니다:필수 사과 대 오렌지 비교.

ActiveRecord는 데이터베이스 쿼리 수행의 용이성에 중점을 둡니다. 대부분의 기능은 모델 클래스 자체에 집중되어 있으므로 개발자가 데이터베이스나 이러한 작업의 영향을 깊이 이해할 필요가 없습니다. ActiveRecord는 기본적으로 많은 작업을 암시적으로 수행합니다. 이렇게 하면 시작하기가 더 쉬워지지만 배후에서 무슨 일이 일어나고 있는지 이해하기가 더 어려워지고 "ActiveRecord 방식"을 따르는 경우에만 작동합니다.

반면에 Ecto는 명시성이 필요하므로 코드가 더 장황합니다. 이점으로 모든 것이 스포트라이트를 받으며 뒤에서 볼 수 없으며 자신만의 방식을 지정할 수 있습니다.

둘 다 관점과 선호도에 따라 장단점이 있습니다. 따라서 사과와 오렌지를 비교한 결과, 이 BAT-tle의 끝 부분에 도달했습니다. BatGirl의 코드명(1989 - 2001)이 .... Oracle이라는 것을 거의 잊어버렸습니다. 그러나 그것에 들어가지 말자. 😉