---
title: 'Designing Lucky: Rock Solid Actions & Routing'
teaser: 'Sick of bugs? See how Crystal and Lucky actions work together to prevent
  common web application errors.

  '
tags: lucky,crystal,web
author: Paul Smith
published_on: 2018-01-15
---

[Lucky](https://github.com/luckyframework/lucky) catches bugs, returns
responses quickly, and helps you write maintainable code. Lucky's actions
help you to process requests and return responses as reliably and
productively as possible.

To make [Lucky
actions](https://luckyframework.org/guides/actions-and-routing/) rock solid,
we looked at the problems we often experience while building apps.

## Problems that we wanted to solve with Lucky

These issues often slow down development or cause embarrassing bugs:

* Route helpers that require documentation to use and aren't discoverable for
  new developers.
* Incorrect or missing parameters.
* Using a path that doesn't have a corresponding route or action.
* Bloated controllers.
* Forgetting to render in complex conditionals.
* Using the wrong HTTP verb for an action.
* Renaming params or routes and forgetting to change a reference.

Lucky aims to help solve these problems with actions.

## Here's what Lucky actions look like

```ruby
class Projects::Show < BrowserAction
  action do
    render_text "show project with id: #{id}"
  end
end
```

This class handles HTTP routing, generating paths, working with params, and
sending responses.

## One class per action

Originally Lucky was going to use controller classes with different methods
for each action, similar to Rails, Phoenix, and many other frameworks.

In the end, Lucky uses one class per action because it allows Lucky to create
methods for route params, generate intuitive and type-safe route helpers, and
infer routes so you don't need a separate routes file. It also keeps the file
clean and focused, which is a nice side effect.

Let's dive into how this works in practice.

## Keeping things productive by inferring the route

_Reliability should not come at the price of productivity_. Creating a
single class per action means a bit more typing to set up the class, and the
creation of another file. This doesn't take _that_ long, but it's still extra
work to both read and write.

To balance out those slowdowns, Lucky handles routing in the action itself.
This means you no longer need to open a routes file, add the route, and
switch back to the controller. Instead, Lucky looks at the class name to
determine the RESTful route for you. For example `Users::Show` handles GET
requests at `"/users/:id"` and `Users::Delete` handles DELETE requests at
`/users/:id`.

This means less typing, less reading, and no switching to route files to
figure out what's going on.

> Don't worry, Lucky can also handle nested resources, namespaces, and custom
> routes like "/dashboard". See more in depth examples in [the guides](https://luckyframework.org/guides/actions-and-routing/).

## Intuitive route helpers

Because the action knows which route it handles, it will generate [route
helpers](https://luckyframework.org/guides/actions-and-routing/#path-and-route-helpers).
So instead of using route helpers like `new_admin_projects_path(project.id)`
in Rails, you'd do this in Lucky: `Admin::Projects::New.with(project.id)`

You use the name of the action and pass the necessary params. This is really
nice in link and redirect helpers:

```ruby
link "New message in this project", to: Admin::Projects::New.with(project.id)
```

Lucky will also help guide you toward the right parameters. If you try to
pass the wrong params or misspell something, Lucky will let you know:

```ruby
link "New message in this project", to: Admin::Projects::Messages::New.with(project.id)

# You'll see a compile-time error like this
Expected 2 arguments to Admin::Projects::Messages::New#with

Overloads are:
  Admin::Projects::Messages::New#with(project_id, id)
```

> This may not be as helpful to experienced devs, but it is extra
> helpful to new developers. It's easier to remember, and Lucky will
> help you find the right way to do things.

## Never worry about the HTTP verb again

One of the more annoying issues I've run into is using the right path, but
the wrong HTTP method.

Here's an example in Rails for deleting a comment:

```ruby
link_to "Delete", comment_path(@comment)
```

**Can you spot the issue?** The path is right, but I forgot to specify the HTTP
verb. This is especially confusing for team members that are new to web
development or REST.

In Lucky, the HTTP verb is automatically used in links, forms, and buttons. You
never have to even think about it. _It just works_.

```ruby
# The right verb (delete) is automatically set for you
link "Delete comment", to: Comment::Delete.with(@comment.id)
```

## Catch errors in conditional responses

I was working on a project where we handled the request differently based on
a number of conditionals. It looked something like this:

```ruby
# Simplified for this example
def new
  if user.present? && sso_enabled?
    redirect_to saml_provider_login_url
  elsif user.present? && !sso_enabled?
    flash[:error] = "This email address does not have SSO enabled"
    redirect_to :back
  end
end
```

Can you spot the error? There is no final `else` so Rails tries to render the
default view for this action. This was not something we tested for or
expected so we didn't add a view and we got a failure in production.

In Lucky, actions must return a response. So if you did something like this
in Lucky:

```ruby
class MyAction < BrowserAction
  action do
    if user.present? && sso_enabled?
      redirect to: SamlProviderLogin::New
    elsif user.present? && !sso_enabled?
      flash.info = "This email address does not have SSO enabled"
      redirect to: SignIns::New
    end
  end
end
```

Lucky will inform you that there is a problem:

    MyAction returned Lucky::Response | Nil, but it must return a Lucky::Response.

    Try this...

      ▸ Make sure to use a method like `render`, `redirect`, or `json` at the end of your action.
      ▸ If you are using a conditional, make sure all branches return a Lucky::Response.

This means you need to write fewer tests, and you will have fewer bugs in
production.

## Automatically generated param methods

Generating routes in the class also allows Lucky to [generate methods for the
params in the
path](https://luckyframework.org/guides/actions-and-routing/#path-parameters).

Here's what Lucky does for a `Users::Show` action:

```ruby
class Users::Show < BrowserAction
  action do
    render_text "Find a user with id of: #{id}"
  end
end
```

Lucky will generate an `id` method for accessing the id param because it has
to be there for the route to match. This means you can be 100% sure that if
you call `id`, the param will be there.

This may not seem that helpful, but it gets nicer when you use more complex
routes. For example, if you create a `Projects::Messages::Index` action,
Lucky will create a message_id method. Now, you don't ever need to worry
about a typo in the param name, or accidentally trying to use `id` (which
doesn't exist for that path because it is an index). It's a little thing, but
it means you get clean looking actions and helpful errors during development.

## We think you'll love Lucky

Check out the [guides](https://luckyframework.org/guides/overview/) to get
started, or learn more about [why
Lucky](https://luckyframework.org/why-lucky/) was made.
