Creating our Poll schema
A schema in Ecto needs one use statement and one import statement by default. In addition, we want to namespace the modules that we define for our new schema with the name of the app (but not the web app!) and the name of the context. We created votes as the subdirectory under the app "Vocial", so we'll create our module as Vocial.Votes.Poll. Next, we need to tell Ecto that this file will be using all of the Ecto functions and helpers that are available to us.
The schema will also be in charge of telling Ecto how to build changesets to modify data in our database, so we'll need to include that functionality via an import statement. We’ll also want to make our lives easier by including an alias for our full module definition so that we can just say %Poll{} instead of %Vocial.Votes.Poll{}. So we’ll start off building our file like this:
defmodule Vocial.Votes.Poll do
use Ecto.Schema
import Ecto.Changeset
alias Vocial.Votes.Poll
end
Next, we need to tell Ecto how to read rows from our database and our polls table as Elixir data structures, so we need to define a Schema. Let's add the following block to our file:
schema "polls" do
field :title, :string
timestamps()
end
Now we've told Ecto how to define our data and how to pull information out of the database. The final piece that's required for any schema file in Ecto is defining the changeset. Let's add a changeset function that will tell Ecto how to make modifications and insert or update those rows in our database. Typically, a changeset takes in the Struct representing the data structure you want to modify and the attributes you want to give to that data structure. You can think of it this way: a changeset always performs modifications, so if you're creating a new row in the database, you'd start with a blank object and roll your changes on top of that. If you're modifying an existing row, you'd take the Ecto representation of that existing row and perform modifications on top of that!
Next, your changeset has to take that initial representation and determine what columns are allowed to be modified (this is because we may have something like virtual fields, which are columns that don't actually exist in the database). This helps prevent against issues like mass assignment, where maybe you don’t want the application to be able to set certain columns as modifiable.
Finally, your changeset should validate whichever columns are required for this to be valid. This will make sure that in the final representation that goes into the database, all of the columns marked as required have values in them. This means that update statements do not have to include updates for columns that already have data in them, so you don't have to worry about validations being performed on columns that already have data in them! All of this talk is great and all, but let's take a look at what the code for that actually should be:
def changeset(%Poll{}=poll, attrs) do
poll
|> cast(attrs, [:title])
|> validate_required([:title])
end
We now have a fully-built and fully-functional representation of our Poll object in Ecto! Hooray!