AddThis

Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts

Monday, October 31, 2016

Migrating in Phoenix

Phoenix Migration

This is part of the series on Phoenix, taking you from noob to !noob. Here are the list of posts so far:
  1. Intro
  2. Scaffolding

Today we will look at the migration file that's produced from the scaffolding we performed last time.

Migration File


defmodule PhoenixLibrary.Repo.Migrations.CreateBook do
  use Ecto.Migration

  def change do
    create table(:books) do
      add :title, :string
      add :author, :string
      add :description, :text

      timestamps()
    end

  end
end

At the end of generating the scaffold, we created the file above - priv/repo/migrations/20161014160805_create_book.exs. We are going to take a quick look at it and figure out what it's doing.

In the Rails world, we would have gotten a file like this:


class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|
      t.string :title
      t.string :author
      t.text :description

      t.timestamps
    end
  end
end

What's great is that the same basic thing that we are doing in Rails we are doing in Phoenix. In the Rails version we are creating a class to capture us creating a new books table and in the Phoenix version, we aren't creating a class, but instead creating a module.

In the Rails world, we are extending from ActiveRecord, but in Phoenix we are using helpers from Ecto.Migration. The important method that we are defining is the `change` method. This allows us to go forwards or backwards in our migration, to either create or tear down the table.


# Rails is rake db:migrate
mix ecto.migrate         # Runs migrations up on a repo

# Rails is rake db:rollback
mix ecto.rollback        # Reverts migrations down on a repo

The change method in both worlds take two arguments, a symbol representing the table we are going to create and a block where we create the actual columns in the table. In Rails we get a helper object that we can use to call functions where as in Phoenix we don't have/need a helper and can just call the add function passing in the name of the column and the type. And of course in Phoenix we call the timestamps function to create the created_at and updated_at column. But, in Rails we call the timestamps method on the helper.

In the future, we will dig into the models that are created from the scaffold command.

Friday, October 21, 2016

Phoenix Scaffolding

Phoenix, Let's Build

Welcome to the second installment of our Phoenix walkthrough. Last time we set up Phoenix, our database and saw our default 'Welcome' page for our dummy library application.

This week we will use some more generators and create our first model, views, and controller. Since this will be a library application, the most obvious choice of our first model should be a `book`.

Generate Scaffold


# mix [generator name] [model name] [table name] [attribute name : attribute type]
mix phoenix.gen.html Book books title:string author:string description:text

# in rails we do
#   rails generate scaffold Book title:string author:string description:text

There are a few key differences between the two, although they are very similar:

  • mix: it's the rake/rails of the elixir world.
  • phoenix.gen.html: this is the generator to use. remember that generators are just ordinary elixir scripts. also notice the `html` suffix which uses the script to generate html (as opposed to json -> phoenix.gen.json).
  • Book: this is the model name. notice the upper case. all models are uppercased.
  • books: this is an interesting distinction vs the rails world. WE NAME THE TABLE OURSELVES.
    in rails the name of the table is automatically made to be the pluralized name of the model, like Book (model name) -> books (table name).
    but English is a quirky language and you cannot always just add `s` to the end of the word, like child to children. so there is some complication in how rails needs to figure out to pluralize you model name.
    this is the first example of some bloat that Phoenix does away with, instead of trying to figure out how to pluralize your model name, Phoenix just defers to you, the developer, to name the table.
  • attributes: this is exactly the same as rails. except in Phoenix, if we omit the type, it defaults to `string`.

➜  phoenix_library git:(master) mix phoenix.gen.html Book books title author description:text

* creating web/controllers/book_controller.ex
* creating web/templates/book/edit.html.eex
* creating web/templates/book/form.html.eex
* creating web/templates/book/index.html.eex
* creating web/templates/book/new.html.eex
* creating web/templates/book/show.html.eex
* creating web/views/book_view.ex
* creating test/controllers/book_controller_test.exs
* creating web/models/book.ex
* creating test/models/book_test.exs
* creating priv/repo/migrations/20161014160805_create_book.exs

Add the resource to your browser scope in web/router.ex:

    resources "/books", BookController

Remember to update your repository by running migrations:

    $ mix ecto.migrate

Look at all the nice things that are generated. This is very similar to scaffold generation. You can see theres:

  • controller: book_controller
  • templates: index, show, new, edit, form
  • views: book_view
  • model: book
  • tests: book_controller, book
  • migration
We will eventually tackle them all, but not right now. Let's go ahead and follow the prompts. Open up web/router.ex and add in the new route to our books resource. Notice this distinction from Rails also. In Rails, this is done for you.


  scope "/", PhoenixLibrary do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index

    # added this line here
    resources "/books", BookController
  end

Next run `rake db:migrate`...I mean `mix ecto.migrate`. :)


➜  phoenix_library git:(master) ✗ mix ecto.migrate
Compiling 9 files (.ex)
Generated phoenix_library app

04:42:58.436 [info]  == Running PhoenixLibrary.Repo.Migrations.CreateBook.change/0 forward

04:42:58.436 [info]  create table books

04:42:58.456 [info]  == Migrated in 0.0s

Now start the server and check out your changes!

➜  phoenix_library git:(master) ✗ mix phoenix.server
Compiling 8 files (.ex)
[info] Running PhoenixLibrary.Endpoint with Cowboy using http://localhost:4000
21 Oct 04:46:52 - info: compiled 6 files into 2 files, copied 3 in 2.1 sec

Don't forget to hit the new route -> http://localhost:4000/books. Alright, that's it for now. Next time we'll talk about the model and migration.

Monday, March 14, 2016

A Docker Movie - Starring Chris Pratt

Ships Ahoy

Lately I've been mucking around with a lot of Docker.  If you are unfamiliar, Docker provides a new approach to deploying software.  Prior to Docker, you had to install all of the libs and dependencies yourself manually on the target machine. But with Docker, you assemble containers that together all make up your application.



Think of it like Lego blocks.  You have a block for, let's say Ruby.  And you have a Lego block for Rails, and another Lego block for your database, say Postgres.  And you put all these blocks together and you have your entire app.  So you don't think of installing servers and language runtimes, you instead think of deploying containers (Lego blocks) that all work with each other.  I would imagine that Chris Pratt could play Ruby in this particular movie.  From the Docker website:

"Docker containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in."

A Docker container for Ruby is completely self contained and works by itself.  A Docker container for a Postgres database is completely self contained and works by itself.  Or you could have them both running at the same time and poof they can work together.  And a Docker container is super easy to run.  You simply pull down the image of a container from a repository (typically DockerHub), then run it.  Done.

Let's take a very simple example.  I've been working recently with a design team that was building a prototype with static html, css and javascript.  They had all their code locally and was zipping them up to me so I could open them up on my computer and view them.  But this process was slow and it was very manual.  No one could see the code except whoever they sent the zip to and I always had to bother them for the latest stuff.  I wanted to be less turtle and more cat with my process.




My first thought was to standup an sftp server somewhere that they could copy files to that would then be served up by nginx or something.  That approach is fine but required me to setup an sftp server and nginx and then configure both.  It also required the design team to periodically upload the files so that I could view them.  Things still felt very manual, and I was feeling lazy.  If you guessed my ultimate solution involved Docker, you are correct :)  The instructions below assume you have docker installed already.  I am running ubuntu and the instructions for setting Docker up are great (https://docs.docker.com/engine/installation/linux/ubuntulinux/).

Setup

Nginx

1.  Download the nginx image from DockerHub and install it locally.
docker pull nginx

2.  Run the image
docker run --name prototype /usr/local/code:/usr/share/nginx/html:ro -d -p 8081:80 --restart=unless-stopped -v nginx

There's a lot here, so I'm going to break down the arguments a bit:
Option Explanation
docker run runs the nginx image that we pulled down from DockerHub
--name prototype this is what I named my container.  This makes it easy to start and stop the container bc I can reference it using this name, 'prototype'
-v /usr/local/code:/usr/share/nginx/html:ro this mounts a volume from the host machine (my ubuntu aws instance) and makes it available to nginx.  So the nginx docker container will see /usr/share/nginx/html, but on my real ubuntu machine, it's actually /usr/local/code.  The last bit 'ro' means that docker cannot write to the volume (read only).  One last gotcha, make sure that the docker group can read /usr/local/code on the host ubuntu machine or you will get permission denied errors.
-d runs the container as a daemon
-p 8081:80 port bindings.  the host ubuntu machine will forward port 8081 to the containers port 80.
--restart=unless-stopped if the docker container dies for any reason, docker will attempt to restart it, unless a person stopped it manually using `docker stop [name]`


Source Code

I created a BitBucket repo that the design team could check code into.  I went with bitbucket b/c they have nice free private plans.

CI Tool

I had Jenkins running already for other projects so I just added a new project.  On BitBucket checkins to the repo I created, Jenkins would pull down the code and build it.  Since everything was static and it's a prototype so I don't really care about code quality yet, my build doesn't really do anything.  But what's important is that the Jenkins project has an scp step that scp's the code to my host ubuntu server.  And it scp's all the code to /usr/local/code.

Flow

So to recap:
1.  Design team checks in code
2.  Jenkins pulls down latest and scp's it to ubuntu:/usr/local/code
3.  Nginx Docker container has /usr/local/code mounted correctly and will serve everything in that dir to http://ubuntu:8081

The major wins for me here was that to download and setup nginx was very easy.  And the deploy process was just as easy.  And of course it happens on every checkin so I can see the latest whenever they push.

So Much More

Docker can do so much more than what I've put here.  On my other projects, as part of my build process, I actually have Jenkins create Docker images that I check into DockerHub.  I can then pull them from anywhere and run them; be it another developers laptop, or even production.  And on every machine, it will run identically.   And Docker just added Docker Cloud to make it even easier to deploy your docker images to a cloud like AWS, MS Azure or DigitalOcean.  So if you haven't played with Docker yet, I really recommend doing so.  And getting started is probably going to be a lot easier than you think it would be.