Start a new project

Starting Phoenix Framework for Rails Developers - Lesson 3

You are not signed in! It would mean a lot to me if you signed up through Github
- I would like to see who you are and I might occationally reach out to you with information aboout updates and questions.

Now that Phoenix is installed we are ready to install a new project.

I will call our new project Listz and will be a very simple TODO application. It will basically just have a list of lists and each list can have tasks. So, extremely simple, but sufficient for holding all the course content I want to cover.

To get started with generating the project, I need to run the command:

mix phx.new listz

During the process, it will ask if it should install dependencies. I will just press Y for Yes.

Ones that is done, I need to cd into the project folder and create the database with the command:

mix ecto.create

If something doesn’t work on this step, make sure you have postgres setup correctly and a database user created.

We can also see here that the commands starts with mix. Mix is the build tool that ships with Elixir and is used in most of the commands we will run in this course.

We are now ready to start the server with the command

mix phx.server

If the server starts without any errors, I should be able to visit localhost port 4000 to see the welcome page.

GREAT SUCCESS. I see the welcome page. Now let’s go on to scaffold the our resource.

Generating a scaffold

To stop the the web server, I need to hit ctrl+c twice.

Rails has a neat built in scaffold command that lets you quickly generate a resource with a migration and a CRUD interface.

Even though I rarely use it, I do find it useful to run once in a new application just to have some files to copy paste code from.

That is also what I tend to do in a new Phoenix application.

That command is:

mix phx.gen.html Lists List lists title slug description:text

This command will tell Phoenix that we want to generate a migration for a table called lists with the columns title, slug and description. Description will be a text field and the others varchar fields.

It also tells that the module where the schema will live in will be called List and the context will be called Lists. Together, the List module and the Lists context module will have the same responsibility as a Rails model. That is not 100% correct, but at least that is how you can think of it right now.

Now we can see some files being generated and also some instructions. First I need to add the new routes in the routes file. It doesn’t do that for me but at least it prints out the code I need to paste in.

And I will paste it in under the root route. That is by default the route that points to the page controllers index action.

After that is saved, I can now run the migration with:

mix ecto.migrate

I will get back to what Ecto is but you can see that the migrations has been ran.

I can now start the server again with the same command as before.

mix phx.server

and visit localhost port 4000 slash posts

I have now a fully functional CRUD interface where I can create and update posts.

Generating a scaffold - Part 2

Lets see what this scaffold actually did for me. Just like a Rails scaffold,

it generated a migration file, the schema, controller, templates and tests.

Speaking of tests, we can run tests with:

mix test

And the first time the tests are run, it takes a little longer because the app needs to compile for the tests environment.

Since I didn’t edit any code, I expect all tests to pass.

However, I want to take a step back here and edit a few files.

Let’s start with the migration file. And for that to make sense, I need to roll back the migration. I can do that with the command:

mix ecto.rollback

That will roll back the migration and drop the newly created posts table.

I want to remove the slug attribute from the html and don’t make it required when I post the form.

The migrations lives in the folder:

priv / repo / migrations 

I can open the migration file and in the bottom of the change function, I will add a

create unique_index(:lists, [:slug])

And as a good habit, I also want to add null: false to both the title column and the slug column.

And the reason for that is so that I will never accidently store a record with null values in the database even if I intend to have validations.

Next I will edit the schema file to remove validation for slug and also change so that is set automatically when the title is set.

lib / listz / lists / list 

First, I will remove :slug from both cast and validate_required. The reason is that the slug will be used in the URL and it will be based on what add in the title field.

For this example I will just add a simple slugging function called set_slug.

I will go deeper into what the changeset function is in a later chapter.

For now, all you need to know is that it is a series of functions that the data is piped through before its stored in the database.

First, it casts the values to the correct field types that are described above. It is also responsible for validations and lastly if I want to do a custom modification of a value before I save it in the database.

And that is exactly what I need to do here. I will add my set_slug at the end.

That is a private function and private functions in Elixir is declared with defp.

In the set_slug function, I want to see in the changeset if :title attribute has changed, and if it has, put the :slug attribute.

And that :slug i get from taking the :title and downcase it and replaces empty spaces with a dash. It is not perfect but enough to get me started.

def changeset(post, attrs) do
  |> cast(attrs, [:title, :content])
  |> validate_required([:title, :content])
  |> set_slug()

defp set_slug(changeset) do
  case fetch_change(changeset, :title) do
    {:ok, "" <> title} -> put_change(changeset, :slug, transform_url(title))
    _ -> changeset

defp transform_url(title) do
  |> String.downcase()
  |> String.replace(" ", "-")

Updating the tests

I want to remove the attributes from the test that we are going to set automatically anyway.

I should now be able to get the test passing with running

mix test

This concludes this part. We have added some custom behaviour to our app.