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!

ActionMailer & Inline Attachments

This might have been a problem that arose from not looking very closely at ActionMailer documentation, but on Saturday, we did a deploy that ended up sending our customers emails with a ton of attachments.

So what happened? We were adding the inline attachments in a before_filter. That was fine. Except that we were adding ALL the inline attachments in a before filter, not just the ones that were needed for every email (say our logo). What we actually wanted to do is add the email specific attachments to just the individual methods within our UserMailer. So if our welcome email includes a certain image, we add it using inline attachments right in the method, like so:

class UserMailer &lt; ActionMailer::Base
  before_filter :inline_attachments

  def inline_attachments
    attachments.inline['logo.png'] = File.read('app/assets/images/mailers/logo.png').force_encoding('utf-8').encode
  end
  
  def welcome_email(user)
    attachments.inline['image.png'] = File.read('app/assets/images/mailers/image.png').force_encoding('utf-8').encode
    @user = user
    mail(subject: "Welcome!", to: @user.email, from: "admin@test.com")
  end
end

More Favorite Gems

Here are another three of my favorite gems:

  1. chronic - Chronic makes me want to do a happy dance almost every time I use it. It basically take text and finds a way to parse it as a date (if possible). Ever wanted to grab ‘next tuesday’? Chronic can do that for you.
  2. pg_search - Hadn’t used it before, but then I had a very intense desire to rid my project of Solr (UGH THE WORST) and this gem did the trick. It makes it super easy to add full text search to your website if you have a postgres database.
  3. jbuilder - I’ve been trying to make any new service respond with JSON (ahem, JSON API) and jbuilder is the perfect tool for creating those responses. Definitely makes my life easier :D

Got any gems that you love?

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"

Optimizing Your Rails App

NOTE: This is only a short post on one thing that I did that drastically reduced memory leaks and helped the performance of my app. if you want to go more in depth, I would recommend Alexander Dymo’s Ruby Performance Optimization.

About a month ago, I became the lead (and only dev) at a small startup. I emphasize small because that’s important to what comes later. Soon after I started, I was seeing a load of Heroku R14 errors... to the tune of 200+ per day. At first I just paid for one more dyno... then another dyno. When I got to seven, I realized that I needed to do something else and fast, because we couldn’t keep upping the dynos every time we got a little bit more traffic. I was also still seeing a TON of R14 errors. Again, I’ll emphasize small, because we really don’t have the large user base that would be actually causing us problems like this. We were just having massive memory leaks. So, time to dig into the code!

The main issue seemed to be a function that saved all recipes that a user could eat (i.e.: they weren’t allergic, didn’t contain any of their dislikes, etc). We were originally determining this by doing something like this:

recipes = Recipe.all
recipes.reject! do | recipe |
  recipe.contains_allergens?
end
recipes.reject! do | recipe |
  recipe.contains_dislikes?
end
# and so on...

So what's the better, safer way to do this? Just build a query! ActiveRecord only runs a query when you perform an action on it, so I could build the whole query and then have my only action be saving it to User.recipes. Example:

user_recipes = Recipe.joins(:ingredients)
user_recipes = user_recipes.where('ingredients.id not in (?)', allergies.map(&:id))
user_recipes = user_recipes.where('ingredients.id not in (?)', dislikes.map(&:id))
# and so on...

This is a gross simplification of the actual code. I know that seems like a silly trivial change, but, no joke, it sped our tests alone up by 4 minutes. Why? Because previously, when we were doing all the rejects, Ruby was saving a new variable in each iteration. Also, while I forgot to test my hypothesis, I'm pretty sure that since recipes was an attribute of User and we were assigning to a 'variable' recipes within the User model, that it was actually saving during each call... also super memory intensive.

So what can you do? If you have some code in your app that you think is slow, you can test it using this benchmark script. If you have that added to your lib directory, just call it like this:

Measure.run do
  Recipe.last #whatever code you want to test
end

The output will look like this:
{"2.2.2":{"gc":"enabled","time":0.04,"gc_count":0,"memory":"0M"}}

To translate:
{ruby-version: {"gc": are_garbage_collectors_enabled?, "time": time_it_took_to_run, "gc_count": total_num_of_garbage_collectors, "memory": amount_of_memory_used }}

On Interviewing Developers

Just read this excellent article by Eric Elliot, Why Hiring Is So Hard In Tech. Here’s my favorite part:

Interviewing

These don’t work:

  • Puzzles and riddles

  • Whiteboard code tests

  • Big O notation quizzes

  • Detailed quizzes about the mystery corners of the language

These work great (in order of value):

  • Pair program with candidate on an actual issue from your ticket queue (let the candidate drive)

  • Code samples / OSS contributions

  • PAID Sample project assignment (err on the side of paying fairly — say $100+/hour for estimated completion time — if the problem should require 2 hours to complete, offer $200)

  • Review past work / portfolio

  • Read candidate blog, publications, watch candidate talks

  • Ask candidate for input on a real problem you’re currently working on

  • Ask specific questions about software problems and solutions from candidate’s resume

IMHO puzzles and riddles as part of coding tests are the WORST. First off, no one is thinking their best during an interview. Second, solving riddles is (bizarrely enough) not a regular part of a dev job. Neither is whiteboarding (at least not like it’s done in interviews) nor chats about Big O notation. If you are interviewing developers you should ask questions that relate to what they actually might do. Recently, when I’ve been talking to candidates, I have been talking about actually problems that we have and it’s definitely helped me judge how great a candidate will be by their suggestions.

If you are looking for a job as a developer or hiring one, you should absolutely read this article.

Stop Undervaluing Yourself

TL;DR - Almost all interviews suck, don’t undervalue yourself because your interviews aren’t going that great.

I had a talk with one of the women in my programming group last night. She has a masters in CS, almost 2 years of work experience, and was still referring to herself as a junior developer. I spent about 30 minutes just repeating to her that she was not junior, just a regular developer… who does have experience! And education!

What prompted this total downturn in self-confidence? Interviews. Unless you are at the top of your game (in that case, good for you!), programming interviews are some of the most stressful things ever. More so than most other jobs, a rejection usually ends up sounding like they just don’t think you are smart enough, versus not a good fit. During my last round of interviews, right before I got a job, I had a bad run and starting feeling like I was in the wrong industry, stupid, and that no one would ever hire me again. On a normal day? Almost never feel like that. But both times I’ve done extensive interviewing, that is always the end result.

Why do I bring this up? Because if you are interviewing right now, don’t let the interviewers get you down. It’s not your intelligence or talent that they are rejecting. No one can really get to know you during an interview. At some point, you’ll go into an interview and they will ask only questions you can answer (or can figure out eventually) and that will be the place that is a good fit for you. Not the place that asks you weird logic puzzles until you are so mentally exhausted you can only babble on about how ‘code is nice’. Interviews aren’t just for the company… you are interviewing them. If the interview is super stressful, then it’s probably not the right place for you… but you will find that right place!

Nested loops

Is it possible to do nested loops? Absolutely! Should you seriously reconsider your code and make sure there’s not another way to do it if you are thinking of using nested loops? ABSOLUTELY! Sometimes you have no choice… but more often, you just need to sit back and think about a better way to do what you want. Here’s a bit of an example:

I work at a nutrition company. We have meal plans, meal plans have recipes, recipes have ingredient measurements, and ingredient measurements have ingredients. In one part of the app, we were trying to consolidate all the ingredients in a meal plan and their measurements. Originally, that involved something like this:

  1. Get all the recipes
  2. Get all the ingredient measurements in that recipe
  3. Get all the ingredients
  4. For each ingredient, get all the ingredient measurements, then loop through those and consolidate

Easier and less memory-intensive way of doing this:

  1. Get all the recipes
  2. Get all the ingredient measurements in that recipe
  3. Loop through those once and tally them up using a hash, so your end result looks like this:
    {'1': [[1, 'cup'], [2, 'ounces']]}
    

Seriously, you should have a staging environment

 Having a good staging environment for a production app is vital. Ideally, it is almost identical to production, with the exception being test credentials if using a service like Stripe. When I came in, we had a staging environment. Ok, great! Except that we had no data in it, so we couldn’t actually test anything but the most basic interactions. It was somewhere between very difficult to impossible to test if bugs had been fixed. It was also using the production environment variable, so it was actually indistinguishable from production… but luckily, we were using test Stripe keys!

How we fixed it (in our Rails app):

  1. Copied production.rb to staging.rb and replaced   config.action_mailer.smtp_settings with the settings from Mailtrap. If you aren’t up for Mailtrap (it’s pretty rad, I would definitely recommend it), you can also use just plain old gmail.
  2. 2) If you are using the rails_12factor gem, don’t forget to add it to the staging environment in the Gemfile, like so:
    group :staging, :production do
      gem 'rails_12factor'
    end
    If you don’t do this, you will (like me) end up with your staging server being unable to find any of your assets and getting a ton of 404s in your console. Whomp whomp.
  3. Set a cron job to regularly grab data from production. Here’s an example of how we are doing it:
    heroku maintenance:on --app STAGING_APP_NAME
    heroku pg:backups capture --app PROD_APP_NAME
    heroku pg:backups restore `heroku pg:backups public-url --app PROD_APP_NAME | cat` DB_NAME --app STAGING_APP_NAME --confirm STAGING_APP_NAME
    heroku run rake db:sanitize_passwords --app STAGING_APPNAME
    heroku run rake db:migrate --app STAGING_APP_NAME
    heroku maintenance:off --app STAGING_APP_NAME 
    
    where DB_NAME is something like HEROKU_POSTGRESQL_JADE. This type of script is only relevant if using heroku and postgres, but you can do something similar if using other providers/databases. This post goes over a different way to do it if you are using mysql. ‘rake db:sanitize_passwords’ would be a task of your own creation to sanitize all the passwords of the users (very important) and also (less important, but still good for privacy) remove identifying data.
  4. Add staging to your database.yml. This should basically be a copy of production, but the database name should be something like #{product}_staging.

I’ve probably missed a few small things, but after that, basically just look for 'production’ in your app and add matching bits for 'staging’.

UPDATE: I did miss something after all. One of my former coworkers brought this to my attention: '3a) Rake task to sanitize/de-identify data, reset passwords?’. In the script I’m actually running, I am updating the credit card information so each customer just has a Stripe test account with a atest card instead of linked to their actual account, but I definitely did forget to reset passwords. Another reason this is useful is that, if you reset all your staging passwords to the same thing, you can login as any user on your test environment to debug any issues they may be having.

Favorite Gems That You Should Absolutely Be Using

  1. Annotate Models
    I didn’t realize how much I loved Annotate Models until I moved to a project where we didn’t have it. Annotate Models basically adds the schema to the bottom of the models and the factories. I LOVE IT. The amount of times I’ve been looking through a model and trying to remember what fields I actually have is immeasurable.
  2. Guard
    Guard can be a pain in the ass. Sometimes it runs all your tests when you don’t want them to be run. But damn if it doesn’t help when you are refactoring a ton of your app and sorta wondering which tests you broke (answer: all of them). I’ve been doing a lot of refactoring and basically wish I had added Guard to the project on Monday instead of Friday.
  3. Paloma
    Paloma is another one I didn’t realize I would miss until it was no longer in my project. All of a sudden, instead of having all my javascript in a particular place, it’s scattered throughout the app, who knows where. I’m adding Paloma to the app I’m working on right now because, dammit, I need this level of organization!

Misconceptions about and, or, &&, and ||

Everyone should read this wonderful post by Avdi Grimm about ‘and’ and ‘or’ operators in Ruby. IMHO they are underutilized, partly in thanks to style guides that say ‘NEVER USE THESE!!!’. You absolutely should use them when it is the right time and place. As Avdi mentions, they are wonderful control-flow operators, so don’t shy away!

New Gig!

I just started a new job at an early stage startup. The app was actually built by a junior developer, so I will probably be posting some of the code that we are changing, since it will cover mistakes that junior devs often make.

First up: listen to Sandi Metz  and try to limit the number of objects instantiated in your controllers. Sometimes you need more than one, but 5+ is usually preventable.

SitePrism is awesome and you should use it

I mentioned SitePrism yesterday and so I wanted to go in a little more depth. From my example yesterday, I used an inventory show page and an inventory index page. I had a find_inventory method on inventory index and a displayed? method on inventory show. I actually reconsidered the displayed? method and so I’m going to show how to use SitePrism to validate that the page has loaded in a different way.

Let’s look at the InventoriesIndex SitePrism page:

module Inventories
  class InventoriesIndex < SitePrism::Page
    set_url '/inventories'
    set_url_matcher %r{/inventories}
    section :list, "#inventories-list" do
      elements :inventories, '.inventory'
      def selector_for_inventory(inventory)
        "#inventory_#{inventory.id.to_s}_row"
      end

      def find_inventory(inventory)
        find selector_for_inventory(inventory)
      end
    end
  end
end

This makes my tests much each to read. Instead of typing

find "#inventory_#{inv.id.to_s}_row"

in my test, it instead looks like:

find_inventory(inv)

SO MUCH PRETTIER!

Let’s look at how SitePrism can help our InventoryShow page:

module Inventories
  class InventoriesShow < SitePrism::Page
    set_url '/inventories/{id}'
    set_url_matcher %r{/inventories/\d+}
    
    section :inventory_info, '#inventory-information' do
      element :name, '.inventory-name'
    end
  end
end

With just that little bit, we can validate that the page loaded what we want:

expect(page.inventory_info.name.text).to include inv.name

Overall, so much nicer than having all the css/xpath selectors all over your RSpec tests. Check it out!

Integration Tests: Cucumber vs. RSpec

I’ve recently started writing integration tests in RSpec instead of Cucumber and I have been LOVING IT. It’s taken much less time and a lot less terrible code. Here is what I need for a test using Cucumber:

features/show_inventory.feature

Background:
    And I am signed in as a user
    And an inventory "I" exists

Scenario: I can view the inventory page from the inventory list page
    Given I am on the inventories page
    When I click on the inventory row for that inventory "I"
    Then I should be on inventory "I"'s page

features/inventory_steps.rb

Given %r{^I am signed in as an? #{capture_model}$} do |model_name|
  user = create_model model_name
  login_with(user.email, user.password)
  @current_user = user
end

When %r{^I click on the inventory row for #{capture_model}$} do |model_name|
  inventory = find_model model_name
  App.inventories_index.list.find_inventory(inventory).click
end

The steps that aren’t covered manually are covered using a tool called pickle. Pretty useful, but can also be sorta frustrating. We also use SitePrism for both styles of tests and if you aren’t using it, you should reconsider.

So what does an RSpec integration test look like? If I was covering the same simple problem, here’s what I would do:

spec/features/user_inventory_spec.rb

require 'rails_helper'

RSpec.feature 'load inventory as user', :js do
  let(:user) { create :user }
  before do
    3.times do
      create :inventory
    end
  end
  
  scenario 'select inventory and view the page' do
    Sessions::SessionsNew.sign_in user.email, 'my_test_pass'
    inventory = Inventory.first
    
    Inventories::InventoriesIndex.new.tap do |page|
      page.load
      page.list.find_inventory(inventory).click
    end
    
    Inventories::InventoriesShow.new.tap do |page|
      page.displayed?
    end
  end
end

And that’s it. I just have to have the SitePrism pages for Sessions::SessionsNew, Inventories::InventoriesIndex, and Inventories::InventoriesShow designed correctly as well. But those I will use multiple times in other tests, so after the inital setup, I save a lot of time. In this case, I had already set the pages up while we were using Cucumber, so I didn’t have to do anything new to start writing tests in RSpec.

TL;DR unless you have a PM that is actually writing your Cucumber tests, just do them in RSpec. You’ll save yourself many a headache.

Eliminating Extra Code with each_with_object

I’ve been listening to Avdi Grimm’s Confident Ruby screencast and came across a function I’d never really used before: each_with_object. each_with_object allows you to iterate over a collection, passing the current item and the memo (the object that was passed in) to the block. The memo must be a mutable object, like an array or hash, not an immutable object such as a string or integer.

Here’s an example:

Without each_with_object, I have to set up a result hash, return if what I’m attempting to iterate over is a blank array, and return the result hash at the end.

def get_results(users)
  users = users.to_a
  result = {}
  return result if users.blank?

  users.each do |u|
    result[u.result_id] = u
  end

  result
end

With each_with_object, I am able to eliminate three lines of code, practically cutting my method in half. I instantiate the empty hash by passing it to each_with_object and each_with_object also does the work of returning the finished hash.

def get_results(users)
  users = users.to_a

  users.each_with_object({}) do |u, result|
    result[u.result_id] = u
  end
end

Anonymous Classes in Ruby

I’m currently watching Dave Thomas’ ‘Ruby Meta-Programming’ screencast from PragProg and he just said something that sorta blew my mind. It both made sense and I hadn’t quite realized that Ruby works this way.

Let’s say you declare a string:

person = "Jennifer"

Then I can also create a method only on person:

def person.job
  'programmer'
end

And then when I call person.job, it returns ‘programmer’. Pretty cool, right? Apparently Ruby does this by creating an anonymous class. So when I’m looking for methods on person (which is a String), it first checks this anonymous class, then the String class, then Object, then BasicObject. I’m not quite sure of a real-life use case for this, but the idea is pretty neat.

INSTA-UPDATE:

Even cooler: if I define another method (say person.hobby), that will be put into the same anonymous class as person.job. Ruby will not create a new anonymous class for each singleton method; it knows that they should be part of the same class.