Skip to content

Instantly share code, notes, and snippets.

@paulcsmith
Created January 18, 2019 23:52
Show Gist options
  • Save paulcsmith/0d7a0ef107e4e3f7e0ee36c555d78e8d to your computer and use it in GitHub Desktop.
Save paulcsmith/0d7a0ef107e4e3f7e0ee36c555d78e8d to your computer and use it in GitHub Desktop.

Guides: Quickstart - build a discussion forum

#lucky

First things first

  • If you want to follow along, install Lucky
  • Learn some Crystal from the official docs if you’re not already familiar

You can also just read through this to see if Lucky looks interesting.

What we’re building

We’ll be building an application that allows users to create new forums, start and comment in discussions, and upvote answers. It’ll also send email notifications and go over how to test the application. We’ll hit on most of what it takes to build an application in Lucky.

Create a new Lucky application

Let’s call our new app “Chatter”. To create a new application, run lucky init chatter . This will create a new Lucky application with files and folders that will help you create server rendered HTML and JSON APIs.

Follow the install instructions that appear after the application is created and you should see the Lucky name and logo after running lucky dev

Want to build just an API with no front end? Use the —-api option when creating the app. Check out the “building a JSON API” guide.

Customizing the generated User model

Lucky generates files for signing in, signing up, and resetting your password. To do that, Lucky also generates a User model. By default it only has an email, and encrypted_password.

In our application, we also want to require people to set a username, and optionally add their full name.

Add username and full name to the User model

The generated User model can be customized by adding a few new columns with the column macro:

# in src/models/user.cr
class User < BaseModel
  table :users do
    # These columns are in the generated file
    column email : String
    column encrypted_password : String
    # These are the new ones we’ll add
    column username : String
    column name : String?
  end
end

You’ll note that we add a ? to the type for the user’s name. That tells Lucky that the name column might be nil. The username on the other hand does not have a ? because it is a required field.

Adding the new User columns to the migration

Lucky uses “migrations” to manage tables, columns, and indices your app will need. Lucky generates a migration that creates the user table. Let’s modify it to add our new columns. You can find this migration in the db/migrations folder:

# in db/migrations/create_users_xxxxxx.cr
class CreateUsers < LuckyMigrator::Migration
  table :users do
    # These columns are in the auto generated file
    add email : String, unique: true
    add encrypted_password : String
    # These are the new ones
    add username : String, unique: true
    add name : String?
  end
end

You’ll note that the migration definition is similar to the model. This is done to minimize the amount of syntax you need to learn.

When Lucky sees a migration with a type that ends with ?, it will allow the column to have null values in the database.

When the column type does not have a ? Lucky will set the column to NOT NULL .

Lucky can do a lot. This guide will skip many of the options available in Lucky so that it’s easier to get going. Check out the migrations guide for an in-depth look at migrations and the available options.

Running the migration

When we create the app, Lucky runs all migrations when we run bin/setup. That means our users table was already created. Since we don’t have any records in the database, let’s rollback our migration and rerun it. his is such a common thing to do during development that we have a task that does this: lucky db.redo. This runs lucky do.rollback and lucky db.migrate.one.

List all the topics

Our forum will allow people to post multiple topics. First, let’s create a new Topic model and database table.

Create topics table with LuckyMigrator

First, run lucky gen.migration CreateTopics to generate a migration.

The generator will create a default migration. Let’s modify it to create a topics table

class CreateTopics::V11111 < LuckyMigrator::Migration
  def migrate
    create :topics do
      add title : String
      add_belongs_to creator : User
    end
  end

  def rollback
    drop :topics
  end
end

create will automatically add id, created_at and updated_atcolumns.

Since we want our topics to be associated with the user that created them, we use add_belongs_to. We give the name of the relation (creator) and the type (User). Lucky will then create an indexed column called creator_id that has a foreign key to theid column on the users table.

Now that the migration is written, run lucky db.migrate to run the migration and create the table.

Create the Topic model

Let’s create our model with lucky gen.model Topic. This command will create 3 files: model, form, and query. For now we’ll focus on the model:

# in src/models/topic.cr
class Topic < BaseModel
  table :topics do
    column title : String
    belongs_to creator : User
  end
end

You’ll notice that we use the same syntax in migrations as we do in the migration. This tells Lucky what column to use and what model type it should be.

Create an action for showing all topics

Let’s create an action with lucky gen.action.browser Topics::Index. This will create a bare bones Topics::Index. Let’s make it do something:

class Topics::Index < BrowserAction
  action do
    render IndexPage, topics: TopicQuery.new
  end
end

The action macro will automatically create a route to this action. I’m this case, /topics.

The render method will render the IndexPage and pass all the topics to it.

Link to more in-depth guides

Create a page to list all topics

Let’s create a new page with lucky gen.page Topics::IndexPage. We’ll edit it to render some topics:

class Topics::IndexPage < MainLayout
  needs topics : TopicQuery

  def content
    h1 “All Topics”
    ul do
      @topics.each do |topic|
        li topic.title
      end
    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment