Is it Worth to Build a Rails Clone in Crystal?


I've been working on building a web framework in Crystal called Iceberg. It aims to be a full-stack MVC web framework like Rails. However, I keep running into problems that make me think: Is it worth to build a Rails clone in Crystal?

Rails is highly productive

The reason why I build my own rather than contributing to other frameworks in Crystal like Amethyst or Moonshine is that I am not quite satisfied with their syntax or DSL. Not DRY at all. Rails is so convenient that every Ruby developer is happy with it. Perfect DSL for quickly building a web application. It's really hard to find another design pattern to be as productive as Rails.

In this case, it's so tempting to build a Rails clone and enjoy the performance of Crystal and productivity of Rails.

The problems

However, there are several problems that we have to face in Crystal.

Crystal is a static and compiled language. That makes it impossible to be as dynamic as Rails. Many metaprogramming methods are unavailable in Crystal, for example, call, class_eval, and many other dynamic ways to define extra classes and methods. Even though there is a concept of Macros in Crystal, you still have to decide everything at compiling stage.

The crucial point in being not dynamic is that you can't start looking for controller actions only after a request comes in. In Rails we don't have to restart the server if we make some simple changes in controllers or views. Exceptions will be raised once there is a problem. This is the reason why we can write something like:

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def show
    @post = Post.find(params[:id])
  end
end

Beautiful syntax, neat and nicely done! However, this pattern may not be possible in Crystal right now. We can see from current web frameworks in Crystal that they all need a bit extra syntax to achieve the same result.

In Moonshine, for example:

class HomeController < Moonshine::Base::Controller
  def initialize
    @viewcount = 0
    @router = {
      "GET /" => ->get(Request),
    }
  end

  def get(req)
    @viewcount += 1
    ok("This page has been visited #{@viewcount} times.")
  end
end

# Bind controller to the app object
app.controller "/", HomeController.new

That is clearly a different logic in this. We have to combine router and controller together to setup the code. From the view of elegance, this might be not as convenient as Rails.

Amethyst does it better in a Rails-like syntax:

class WorldController < Base::Controller
  actions :hello

  view "hello", "#{__DIR__}/views"
  def hello
    @name = "World"
    respond_to do |format|
      format.html { render "hello" }
    end
  end
end

class HelloWorldApp < Base::App
  routes.draw do
    all "/",      "world#hello"
    get "/hello", "world#hello"
    register WorldController
  end
end

I would say it is trying to build a Rails clone, since the syntax are pretty much alike. However, if you have had any experience in Rails, you would see that the actions method and register WorldController parts are the price for a Rails-like syntax.

We Need a Differernt Logic

Even though Crystal is a clone of Ruby that takes advantage of its syntax, I would say a web framework for a static-typed, compiled language is totally different. We need logics like Revel in Go or other static-typed language frameworks to create a different MVC (or not MVC) full-stack web framework.

Yeah we can build a Rails-clone like what Amethyst is trying to do, but I believe a clone will never be better than the original. Amethyst will one day have to develop new features that takes full advantages of Crystal and at the same time Rails can never achieve.