Getting Started With Play After Working In Rails

I've now been using Scala since November (so a little over 4 months) and Play since January (exactly two months today). When I first started writing this application, I was brand new to Scala. My boss recommended Scalatra since he had some experience. Since I had none, I agreed and got started. I learn by example, so I first went through and found some projects that I could look at and base my project off. With Rails, this was easy. The Rails Guides are FANTASTIC (I miss them so much). With Scalatra, this was much more challenging. I made some progress, but then I came to a screeching halt, which caused my boss to post to Reddit asking for suggestions. Lemme pull out some of my favorite comments:

On stack overflow there are around ~250 questions tagged with scalatra. There are around 15k play framework related questions. You're pretty much on your own if you go scalatra.
Akka HTTP you pretty much have to have a PhD to understand.
Play lacks a coherent, functional API, documentation for a good 60% of it, and completely lacks the composability and ease of use of alternative frameworks like http4s. Most of these problems with Play are due to poor planning, and being a Lightbend technology which is contorted to work with Akka(and akka-http), yet another poor Lightbend tech. It's a pervasive rot in the community, just like Akka.

GREEEAAAAAAATTTTT. Anyway, we decided to try Play It has documentation (the bar, it is low), at least one book written about it, and some decent templates. I migrated my project over to Play and got going. One of the major differences I noticed between Play and Rails is that Play is not very opinionated. In general, if you look at a Rails project, everything is generally in the same place. Pretty much everyone uses ActiveRecord and the RDMS you choose doesn't really matter. With pretty much any Rails project, you can initialize the database with rake db:create. This is not the case for Play. As far as I can tell, you have to create the database and then Play will run evolutions (migrations). The real problem I have is that there also is no standard. Slick is very popular, but we decided to use the newer kid in class, Quill. And I couldn't find a single example of someone using Play 2.6, Quill, and PostgreSQL. And Play 2.6 is a breaking release from Play 2.5. I found one template that used Play 2.5, Quill, and PostgreSQL, but it broke when I upgraded to Play 2.6. Right now I'm having some database connectivity issues, but I'm hoping to resolve those soon. As soon as I get the app working, I'm going to create a template so hopefully, others won't have as hard of a time as I have.

Overall, I sorta wish I was still working in Rails? I love the simplicity of Ruby and how easy Rails makes it to get a decent CRUD app up and running. It definitely would have only taken me one week to make this app in Rails and it's taken four months (and counting) in Scala.

Promo Codes in Rails

When I got tasked with adding promo codes in our Rails apps, the first thing that surprised me was how few posts there were on how to do it. So here I am to fill that gap.

First! We have to create a promo codes model:
class CreatePromoCodes< ActiveRecord::Migration
  def change
    create_table :promo_codes do |t|
      t.string :code
      t.decimal :amount
      t.text :purpose

      t.timestamps
    end
  end
end

And the model:
class PromoCode < ActiveRecord::Base
  # ...
  has_and_belongs_to_many :users
  # ...
end

Then we should add some fields to a user to reference promo codes. For our case, we want to know what promo codes they have used in the past, how much credit they have, and what user referred them.
class AddFieldsToUser < ActiveRecord::Migration
  def change
    add_column :users, :credits, :decimal, default: 0.0
    add_column :users, :referring_user_id, :integer
  end
end

And the updates to the user model:
class User < ActiveRecord::Base
  # ...
  belongs_to :referring_user, class_name: "User", foreign_key: "referring_user_id"
  has_and_belongs_to_many :promo_codes
  # ...
end

Plus one final migration to allow the has_and_belong_to_many to work:
class CreatePromoCodeUser < ActiveRecord::Migration
  def change
    create_table :promo_codes_users, id: false do |t|
      t.belongs_to :user, index: true
      t.belongs_to :promo_code, index: true
      t.timestamps
    end
  end
end

We also have a PromoCodesController. This isn't really doing anything unique, we just need it so we can allow admins to create promo codes.
class PromoCodesController < AdminController
  before_action :set_promo_code, only: [:update, :destroy]

  respond_to :html, only: [:index]
  respond_to :json, except: [:index]

  def index
    @promo_codes = PromoCode.all
    respond_with(@promo_codes)
  end

  def create
    @promo_code = PromoCode.new promo_code_params
    @promo_code.save
  end

  def update
    @promo_code.update(promo_code_params)
  end

  def destroy
    @promo_code.destroy
    render nothing: true, status: 204
  end

  private
    def set_promo_code
      @promo_code = PromoCode.find(params[:id])
    end

    def promo_code_params
      params.require(:promo_code).permit(:code, :amount, :active, :purpose)
    end
end

We then added a route that was specific to adding a promo code (within the user controller). We post to this route whenever a user adds a promo code:
def add_promo_code
  user = User.find(params[:user_id])
  # We decided the easiest way to do referring users
  # was to have codes that looked like this: MYCODE-265,
  # with 265 being the id of the referring user.
  # Not necessarily teh best solution for everyone!
    promo_code, referring_user = params[:promo_code].split('-')
    code = PromoCode.find_by_code(promo_code)
    if code
      if user.promo_codes.include?(promo_code)
        message = 'This promo code has already been used.'
      else
        user.promo_codes << code
        user.credits += code.amount
        if referring_user && user.referring_user_id.nil? && User.find_by_id(referring_user).present?
          user.referring_user_id = referring_user
        end
        if user.save
          message = 'Promo code successfully used!'
        else
          message = 'Error using promo code. Please try again or contact customer service.'
        end
      end
    else
      message = 'Not a valid promo code.'
    end
  respond_to do |format|
    format.js {  flash[:notice] = message }
  end
end

Then, whenever we are getting ready to charge a user, we add code like this:
if @user.credits > 0.0
  if @user.credits >= @price
    @meal_plan.price = 0.0
    @user.credits = @user.credits - @price
  else
    @meal_plan.price -= @user.credits
    @user.credits = 0.0
  end
end

What do referring users get? For us, they get a credit when the person they referred makes their first purchase:
def charge_user(user)
  if user.first_charge? && user.referring_user
    user.referring_user.credits += PromoCode.find_by_code(@user.promo_codes.first).amount
    user.referring_user.save
  end
end

There are a few other smaller things, but that was the bulk. If anyone has any feedback or something they would do differently, I'd love to see it in the comments!

ActiveRecord Joins

Random thing I missed while turning all those selects into regular queries using joins: if you use the names of associations to join tables (ex. recipes.joins(:ingredients)), it creates an INNER join. Why is this important? Here’s my example:

I have recipes with ingredients, and each ingredient might have a subingredient (gluten, soy, etc). Every recipe has ingredients, so an inner join is fine there. However, every recipe does not have subingredients. Since I was doing a recipes.joins(ingredients: :subingredients), I was only getting recipes that did not have subingredients... definitely not what I wanted. Here’s the code:

BEFORE:
Code
Recipe.joins(ingredients: :subingredients).joins(:ingredients)

Resulting SQL
SELECT "recipes".* FROM "recipes" INNER JOIN "ingredient_measurements" ON "ingredient_measurements"."recipe_id" = "recipes"."id" INNER JOIN "ingredients" ON "ingredients"."id" = "ingredient_measurements"."ingredient_id" INNER JOIN "ingredients_subingredients" ON "ingredients_subingredients"."ingredient_id" = "ingredients"."id" INNER JOIN "subingredients" ON "subingredients"."id" = "ingredients_subingredients"."subingredient_id"

AFTER:
Code
Recipe.joins(:ingredients).joins('LEFT JOIN "ingredients_subingredients" ON "ingredients_subingredients"."ingredient_id" = "ingredients"."id" LEFT JOIN "subingredients" ON "subingredients"."id" = "ingredients_subingredients"."subingredient_id"')

Resulting SQL
SELECT "recipes".* FROM "recipes" INNER JOIN "ingredient_measurements" ON "ingredient_measurements"."recipe_id" = "recipes"."id" INNER JOIN "ingredients" ON "ingredients"."id" = "ingredient_measurements"."ingredient_id" LEFT JOIN "ingredients_subingredients" ON "ingredients_subingredients"."ingredient_id" = "ingredients"."id" LEFT JOIN "subingredients" ON "subingredients"."id" = "ingredients_subingredients"."subingredient_id"

UPDATE: As Darren (manvsmachine) mentions, you can also use .eager_load to create LEFT OUTER joins. However, that does give you a different query. It seems to have the same-ish results, but the SQL output is slightly different. It looks something like this:

Code
Recipe.eager_load(:subingredients)

Resulting SQL
SELECT "recipes"."id" AS t0_r0, "recipes"."name" AS t0_r1, "recipes"."instructions" AS t0_r2, ... "recipes"."image" AS t0_r14, "subingredients"."id" AS t1_r0, "subingredients"."name" AS t1_r1, "subingredients"."created_at" AS t1_r2, "subingredients"."updated_at" AS t1_r3 FROM "recipes" LEFT OUTER JOIN "ingredient_measurements" ON "ingredient_measurements"."recipe_id" = "recipes"."id" LEFT OUTER JOIN "ingredients" ON "ingredients"."id" = "ingredient_measurements"."ingredient_id" LEFT OUTER JOIN "ingredients_subingredients" ON "ingredients_subingredients"."ingredient_id" = "ingredients"."id" LEFT OUTER JOIN "subingredients" ON "subingredients"."id" = "ingredients_subingredients"."subingredient_id"