0Pricing
Elixir & Phoenix: Scalable Backend Development · 강의

Repo, 변경 집합 및 쿼리

데이터베이스 상호작용에는 `Ecto.Repo`를, 데이터 검증에는 `Changesets`를 사용하고 `Ecto.Query`로 쿼리를 작성합니다.

Repo, 변경 집합 및 쿼리은(는) CoddyKit의 무료 Elixir & Phoenix: Scalable Backend Development 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Elixir & Phoenix: Scalable Backend Development 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Elixir & Phoenix: Scalable Backend Development 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Ecto.Repo: Database Gateway

In Elixir, Ecto.Repo is your primary interface for interacting with your database. Think of it as the central hub that handles all your database operations.

It provides functions for inserting, updating, deleting, and querying records, abstracting away the specifics of the underlying database technology.

  • Connects your Elixir application to the database.
  • Manages database transactions.
  • Executes Ecto queries.

Saving Data with Repo.insert

To add new data to your database, you'll use Ecto.Repo.insert/2. This function takes a changeset (which we'll cover next) and attempts to save it as a new record.

If successful, it returns {:ok, record}; otherwise, it returns {:error, changeset} with validation errors.

defmodule MyApp.Product do
  use Ecto.Schema
  import Ecto.Changeset

  schema "products" do
    field :name, :string
    field :price, :decimal
    field :stock, :integer, default: 0
    timestamps()
  end

  def changeset(product, attrs) do
    product
    |> cast(attrs, [:name, :price, :stock])
    |> validate_required([:name, :price])
    |> validate_number(:price, greater_than: 0)
    |> validate_number(:stock, greater_than_or_equal_to: 0)
  end
end

defmodule MyApp.SimulatedRepo do
  alias MyApp.Product

  def insert(changeset) do
    if Ecto.Changeset.valid?(changeset) do
      data = Ecto.Changeset.apply_action(changeset, :insert)
      # Simulate successful insert with a mock ID and timestamps
      {:ok, %Product{data | id: 1, inserted_at: NaiveDateTime.utc_now(), updated_at: NaiveDateTime.utc_now()}}
    else
      {:error, changeset}
    end
  end
end

defmodule InsertExample do
  alias MyApp.{Product, SimulatedRepo}

  def run() do
    # Prepare product attributes
    attrs = %{name: "Elixir Book", price: Decimal.new("39.99"), stock: 100}

    # Create a changeset
    changeset = Product.changeset(%Product{}, attrs)

    # Simulate inserting the product
    case SimulatedRepo.insert(changeset) do
      {:ok, product} ->
        IO.puts "Successfully inserted product:"
        IO.inspect product
      {:error, changeset} ->
        IO.puts "Failed to insert product:"
        IO.inspect Ecto.Changeset.errors(changeset)
    end
  end
end

# To run this, execute: elixir -e "InsertExample.run()"

Retrieving Records with Repo

You can fetch records from the database using Ecto.Repo.get/3 to retrieve a single record by its primary key, or Ecto.Repo.all/2 to get multiple records.

  • get(Schema, id): Fetches one record by ID. Returns the struct or nil.
  • all(Query): Fetches all records matching a given query.
defmodule MyApp.Product do
  use Ecto.Schema
  schema "products" do
    field :name, :string
    field :price, :decimal
    field :stock, :integer, default: 0
    timestamps()
  end
end

defmodule MyApp.SimulatedRepo do
  alias MyApp.Product

  def get(id, _query \\ nil) do
    IO.puts "Simulating fetching product with ID: #{id}"
    case id do
      1 -> %Product{id: 1, name: "Laptop", price: Decimal.new("1200.00"), stock: 5,
                   inserted_at: NaiveDateTime.utc_now(), updated_at: NaiveDateTime.utc_now()}
      _ -> nil
    end
  end

  def all(_query \\ nil) do
    IO.puts "Simulating fetching all products..."
    [
      %Product{id: 1, name: "Laptop", price: Decimal.new("1200.00"), stock: 5,
               inserted_at: NaiveDateTime.utc_now(), updated_at: NaiveDateTime.utc_now()},
      %Product{id: 2, name: "Mouse", price: Decimal.new("25.50"), stock: 20,
               inserted_at: NaiveDateTime.utc_now(), updated_at: NaiveDateTime.utc_now()}
    ]
  end
end

defmodule FetchExample do
  alias MyApp.SimulatedRepo

  def run() do
    # Fetch a single product by ID
    IO.puts "\n--- Fetching by ID ---"
    product = SimulatedRepo.get(1)
    IO.inspect product

    # Fetch all products
    IO.puts "\n--- Fetching all ---"
    products = SimulatedRepo.all()
    IO.inspect products
  end
end

# To run this, execute: elixir -e "FetchExample.run()"

Data Safety with Changesets

Ecto.Changeset is a powerful mechanism for validating, casting, and manipulating data before it interacts with your database. It ensures data integrity and security.

Instead of directly passing raw user input to the database, you first pass it through a changeset. The changeset tracks all proposed changes and any associated errors.

  • Casting: Converts input data into the correct Elixir types.
  • Validation: Checks if the data meets predefined rules (e.g., required fields, data formats).
  • Authorization: Can even be used for field-level permission checks.

Casting and Basic Validation

The first step with a changeset is usually Ecto.Changeset.cast/4. This function takes a struct (or an empty struct), parameters (like user input), and a list of allowed fields. It converts the input into the correct types.

After casting, you can apply validations like validate_required/3 to ensure essential fields are present.

defmodule MyApp.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :email, :string
    field :age, :integer
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :age])
    |> validate_required([:name, :email])
  end
end

defmodule ChangesetCastValidateExample do
  alias MyApp.User

  def run() do
    IO.puts "--- Valid Input ---"
    valid_params = %{"name" => "Alice", "email" => "alice@example.com", "age" => "30"}
    valid_changeset = User.changeset(%User{}, valid_params)
    IO.puts "Is valid? #{Ecto.Changeset.valid?(valid_changeset)}"
    IO.inspect Ecto.Changeset.changes(valid_changeset)

    IO.puts "\n--- Invalid Input (missing email) ---"
    invalid_params = %{"name" => "Bob", "age" => "25"}
    invalid_changeset = User.changeset(%User{}, invalid_params)
    IO.puts "Is valid? #{Ecto.Changeset.valid?(invalid_changeset)}"
    IO.inspect Ecto.Changeset.errors(invalid_changeset)
  end
end

# To run this, execute: elixir -e "ChangesetCastValidateExample.run()"

Deeper Changeset Validations

Beyond basic requirements, Ecto offers many validation helpers to enforce specific rules:

  • validate_length/3: Checks string length.
  • validate_number/3: Validates numeric ranges.
  • validate_format/3: Uses regular expressions for complex patterns (e.g., email format).

These help you build robust data models.

defmodule MyApp.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :email, :string
    field :age, :integer
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :age])
    |> validate_required([:name, :email])
    |> validate_length(:name, min: 3, max: 50)
    |> validate_number(:age, greater_than_or_equal_to: 18)
    |> validate_format(:email, ~r/@/, message: "must contain an @ symbol")
  end
end

defmodule ChangesetDeepValidateExample do
  alias MyApp.User

  def run() do
    IO.puts "--- Valid User ---"
    user_attrs_1 = %{"name" => "Charlie", "email" => "charlie@example.com", "age" => "22"}
    changeset_1 = User.changeset(%User{}, user_attrs_1)
    IO.puts "Valid? #{Ecto.Changeset.valid?(changeset_1)}"
    IO.inspect Ecto.Changeset.errors(changeset_1)

    IO.puts "\n--- Invalid User (short name, young age, bad email) ---"
    user_attrs_2 = %{"name" => "Al", "email" => "al.example.com", "age" => "17"}
    changeset_2 = User.changeset(%User{}, user_attrs_2)
    IO.puts "Valid? #{Ecto.Changeset.valid?(changeset_2)}"
    IO.inspect Ecto.Changeset.errors(changeset_2)
  end
end

# To run this, execute: elixir -e "ChangesetDeepValidateExample.run()"

Applying Changeset Actions

Once a changeset is validated, you can use it to perform database actions like inserting or updating records. The Ecto.Changeset.valid?/1 function tells you if the changeset is ready.

If invalid, Ecto.Changeset.errors/1 will provide a detailed list of what went wrong, which is crucial for providing user feedback.

defmodule MyApp.Product do
  use Ecto.Schema
  import Ecto.Changeset

  schema "products" do
    field :name, :string
    field :price, :decimal
    field :stock, :integer, default: 0
    timestamps()
  end

  def changeset(product, attrs) do
    product
    |> cast(attrs, [:name, :price, :stock])
    |> validate_required([:name, :price])
    |> validate_number(:price, greater_than: 0)
  end
end

defmodule ChangesetApplyExample do
  alias MyApp.Product

  def run() do
    IO.puts "--- Valid Product Changeset ---"
    valid_attrs = %{"name" => "Widget", "price" => "9.99", "stock" => "50"}
    valid_changeset = Product.changeset(%Product{}, valid_attrs)

    if Ecto.Changeset.valid?(valid_changeset) do
      IO.puts "Changeset is valid. Proposed changes:"
      IO.inspect Ecto.Changeset.changes(valid_changeset)
      # In a real app, you'd call Repo.insert(valid_changeset) here
    else
      IO.puts "Changeset is invalid. Errors:"
      IO.inspect Ecto.Changeset.errors(valid_changeset)
    end

    IO.puts "\n--- Invalid Product Changeset (missing price) ---"
    invalid_attrs = %{"name" => "Gadget", "stock" => "20"}
    invalid_changeset = Product.changeset(%Product{}, invalid_attrs)

    if Ecto.Changeset.valid?(invalid_changeset) do
      IO.puts "Changeset is valid. Proposed changes:"
      IO.inspect Ecto.Changeset.changes(invalid_changeset)
    else
      IO.puts "Changeset is invalid. Errors:"
      IO.inspect Ecto.Changeset.errors(invalid_changeset)
    end
  end
end

# To run this, execute: elixir -e "ChangesetApplyExample.run()"

Building Queries with Ecto.Query

While Repo.get/3 and Repo.all/2 are useful for simple fetches, Ecto.Query provides a powerful, composable language for building complex database queries.

You start a query with the from macro, specifying the schema you want to query and giving it an alias.

Ecto queries are Elixir expressions that are translated into SQL by Ecto.

defmodule MyApp.Product do
  use Ecto.Schema
  schema "products" do
    field :name, :string
    field :price, :decimal
    field :stock, :integer, default: 0
  end
end

defmodule EctoQueryFromExample do
  import Ecto.Query
  alias MyApp.Product

  def run() do
    # A simple query to select all products
    query = from p in Product
    IO.puts "Basic query structure:"
    IO.inspect query

    IO.puts "\n(Note: This only shows the query structure, not execution against a DB.)"
  end
end

# To run this, execute: elixir -e "EctoQueryFromExample.run()"

Querying: Filtering & Selecting

You can refine your queries using clauses like where to filter records based on conditions, and select to specify which fields you want to retrieve.

  • where: p.stock > 0: Filters records where stock is greater than 0.
  • select: p.name: Retrieves only the 'name' field.
defmodule MyApp.Product do
  use Ecto.Schema
  schema "products" do
    field :name, :string
    field :price, :decimal
    field :stock, :integer, default: 0
  end
end

defmodule EctoQueryWhereSelectExample do
  import Ecto.Query
  alias MyApp.Product

  def run() do
    # Query for products with stock > 0, selecting only their names
    query = from p in Product,
            where: p.stock > 0,
            select: p.name

    IO.puts "Query structure with where and select:"
    IO.inspect query

    IO.puts "\n(Note: This only shows the query structure, not execution against a DB.)"
  end
end

# To run this, execute: elixir -e "EctoQueryWhereSelectExample.run()"

Querying: Ordering & Limiting

To control the presentation of your results, you can use order_by to sort records and limit to restrict the number of records returned.

  • order_by: [desc: p.price]: Sorts by price in descending order.
  • limit: 5: Returns only the first 5 matching records.

These are crucial for pagination and displaying sorted lists.

defmodule MyApp.Product do
  use Ecto.Schema
  schema "products" do
    field :name, :string
    field :price, :decimal
    field :stock, :integer, default: 0
  end

end

defmodule EctoQueryOrderLimitExample do
  import Ecto.Query
  alias MyApp.Product

  def run() do
    # Query for products, ordered by price descending, limited to 3
    query = from p in Product,
            order_by: [desc: p.price],
            limit: 3

    IO.puts "Query structure with order_by and limit:"
    IO.inspect query

    IO.puts "\n(Note: This only shows the query structure, not execution against a DB.)"
  end
end

# To run this, execute: elixir -e "EctoQueryOrderLimitExample.run()"

Quick Check: Ecto Core

Which of the following statements about Ecto.Changeset is TRUE?

Recap: Repo, Changesets & Queries

Great job! In this lesson, you've learned the core components for data persistence in Elixir with Ecto:

  • Ecto.Repo: The gateway for all database interactions.
  • Ecto.Changeset: Essential for casting, validating, and tracking changes to your data, ensuring integrity.
  • Ecto.Query: A powerful and flexible way to construct complex database queries using Elixir syntax.

Mastering these three pillars is fundamental to building robust and reliable data-driven applications in Elixir!

자주 묻는 질문

“Repo, 변경 집합 및 쿼리” 강의는 무료인가요?

네 — “Repo, 변경 집합 및 쿼리” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Elixir & Phoenix: Scalable Backend Development 강의 전체를 잠금 해제할 수 있습니다. Elixir & Phoenix: Scalable Backend Development 강의에는 총 4개의 강의가 포함되어 있습니다.

“Repo, 변경 집합 및 쿼리”에서 뭘 배우나요?

데이터베이스 상호작용에는 `Ecto.Repo`를, 데이터 검증에는 `Changesets`를 사용하고 `Ecto.Query`로 쿼리를 작성합니다. 브라우저에서 직접 실행하는 실습 코드로 Elixir & Phoenix: Scalable Backend Development을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Elixir & Phoenix: Scalable Backend Development을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Elixir & Phoenix: Scalable Backend Development은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“Repo, 변경 집합 및 쿼리” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Elixir & Phoenix: Scalable Backend Development 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Elixir & Phoenix: Scalable Backend Development 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Ecto 스키마와 데이터베이스 마이그레이션
  2. Repo, 변경 집합 및 쿼리
  3. 연관 관계와 내장 스키마
  4. 트랜잭션과 Ecto.Multi
← Elixir & Phoenix: Scalable Backend Development(으)로 돌아가기