Using Models with Custom Primary Keys - TanYewWei/ecto GitHub Wiki
Developer-Assigned Model Primary Keys
In the "Models" section of the README, we saw the following example:
defmodule Weather do
use Ecto.Model
# weather is the DB table
schema "weather" do
field :city, :string
field :temp_lo, :integer
field :temp_hi, :integer
field :prcp, :float, default: 0.0
end
end
This is great for most purposes, but assumes that primary keys are of :integer
type, and will be generated by the database when the Model is inserted into the database. What if you wanted to use Developer-Assigned random :string
primary keys instead?
This can easily be done by using the @schema_defaults
module declaration:
defmodule Weather do
use Ecto.Model
@schema_defaults [primary_key: {:id, :string, []}]
# weather is the DB table
schema "weather" do
field :city, :string
field :temp_lo, :integer
field :temp_hi, :integer
field :prcp, :float, default: 0.0
end
end
Now any Weather
models will require a :string
primary key, which has no default value. It is then up to you the developer to assign an appropriate primary key as needed. Assigning a primary key MUST be done using the Ecto.Model.put_primary_key/2
function.
Using the
put_primary_key/2
function is important to ensure that any associations of a model are properly set when the primary key changes.
Example:
weather = %Weather{temp_lo: 30}
weather = Ecto.Model.put_primary_key(weather, "some-new-id")
Declaring Defaults Across All Your Models
Having to type @schema_defaults
in each of your models gets tiresome after awhile :frowning: So it's common to have another module that declares your @schema_defaults
, and then use
that module instead of Ecto.Model
defmodule MyModel do
defmacro __using__(opts) do
# declare that all models use :string primary keys, and
# all associations should assume :string foreign keys as well
@schema_defaults [primary_key: {:id, :string, []},
foreign_key_type: :string]
# Make sure to use the appropriate Ecto modules
use Ecto.Model.Schema
use Ecto.Model.Validations
end
end
Then we can declare the following modules:
defmodule Post do
use MyModel # instead of Ecto.Model
schema "posts" do
field :title, :string
field :body, :string
has_many :comments, Comment
end
end
defmodule Comment do
use MyModel
schema "comments" do
field :body, :string
belongs_to :post, Post
end
end
And then use create some Comments and Posts like so (assuming Repo
is a valid Ecto.Repo
):
# Create Post
post = %Post{title: "Post 1", body: "Hello!"}
|> Ecto.Model.put_primary_key("post_1")
|> Repo.insert()
# Assign comments
c0 = %Comment{body: "I made a comment", post_id: post.id}
|> Ecto.Model.put_primary_key("comment_0")
|> Repo.insert()
c1 = %Comment{body: "I made a comment", post_id: post.id}
|> Ecto.Model.put_primary_key("comment_1")
|> Repo.insert()
# Fetch post and comments
# don't forget to `import Ecto.Query`
get_post = from(x in Post,
where: x.id == ^post.id,
preload: :comments)
|> Repo.all()
comments = get_post.comments.all
c0 in comments #=> true
c1 in comments #=> true
Notes
- See the
Ecto.Model.Schema
module docs for more information about@schema_defaults
(TODO: update link to v0.2.0 docs instead of source code once released)