RailsConf 2018 Recap

I went to RailsConf last week and it was an amazing experience. DHH's keynote reminded me why I love Rails. Eileen's keynote made me super pumped for Rails 6. And all the talks were a delightful reminder of why I love programming and why I love the Ruby community. Here are some deeper thoughts and notes, divided up by talk:

Note: I'll post links to talks that I reference as soon as they are up.

DHH's keynote

This talk really hit home for me since I was very recently battling with Play for 4 months. One of the things that I do really love about Rails is that I can focus on solving the problem I actually want to solve, not problems that have been solved before (like... authentication). And, while I do agree that junior developers and people just starting out should not have to know SQL, I do think that knowledge of SQL is still useful if you want to be a good Rails developer. Relying solely on ActiveRecord is a mistake.

Crash Course in RSpec: stubs and doubles and mocks -- oh my!

This workshop managed to be both good and not quite what I wanted. I had hoped by the title that there would be a big emphasis on stubbing, but it was more of a footnote. It was a good crash course though and if you don't have much prior RSpec experience, check out Nicole's tutorial.

Interviewer Skills

Jennifer Tu of Cohere gave an excellent workshop on interviewer skills that I have about 4 pages of notes from that I will try to sum up here. One of the first things she brought up was that a team should have specific goals in mind when interviewing:

  • What values does the team have?
  • What characteristics does the candidate have?
  • What actions does the candidate take in certain situations?
  • What makes someone successful on my team?

For each attribute that the interviewers want the candidate have, they should ask questions that dig into how a candidate behaves. For example, if your team values kind feedback, instead of asking "Do you give kind feedback?" or "Are you nice when responding to pull requests?", ask "Have you ever given feedback to someone whose code was not good? What did you do? Why?". If you value independent learning, ask:

  • How do you learn something new?
  • Do you have an example of a time when you ran into code you didn't understand?
  • Share a time when you had a problem dumped into your lap but you had no idea what to do.

Make sure to wrap a question in context to ensure the candidate fully understands what you are asking. For example, one of the attendees wanted independent thinkers and people who would question decisions. They were currently asking this question:

You get a user request to add a blue button. How do you add a blue button?

However, in the context of an interview, someone who would normally question a decision like that might resonable think that the interviewer just wants to know if they know how to add a button to a page in HTML. What they should ask is:

We get a lot of feature requests and they aren't always valid. What would you do if you got a feature request to add a blue button?

Allow interviewees to show the skill if you can. Theoretical scenarios often just end up only showing red flags. Play acting is the better option. For example, if you want to know if someone gives kind feedback, give them some bad code and have them review it. If you want to know how they handle conflict, play act with the two interviewers coming up with conflicting ideas and ask them how they would resolve it.

It is the job of the interviewer to give the candidate the opportunity to show off. Interrupt (politely) if needed. You will be doing them a favor! Here are some possible polite interruptions:

  • I like where you are going with this but....
  • I'm sorry to interrupt, but I'm really curious about...
  • This is interesting, but I really want to hear more about...

You should also be sure to set an agenda and share rubrics with other interviewers ahead of time.

Pairing: A Guide To Fruitful Collaboration

André Arko gave this talk on the best way to pair and, as someone who has paired incorrectly for a while, it was quite interesting. So the basis of pairing is two devs, one machine.

I can't not use this gif even though pairing is not this.

Anyway! You should be actively collaborating. The best way to think of pairing is to think of it as one little meeting. If done right, it should push you to be a better dev and away from bad habits. Above all, pairing needs trust. If you are condescending, that breaks the trust of your pair and makes you a lousy pair. One good way to pair is to have the driver write a test, codes until the test passes, writes a new test, then switch driver to the other person, who then repeats the process. Never say "let me do this quickly by myself." That is not pairing! Help the driver solve the problem and stay on the same page, so you both understand. There's a lot more to this talk, but I think you should watch it yourself 😃

The Practical Guide to Building An Apprenticeship

Megan Tiu built out the apprenticeship program at CallRail and so we get to learn from her experience! To start an apprenticeship program, you need:

  • plan (what are they going to do?)
  • cash (pay them!)
  • buy-in (convince the boss!)

You can sell it by noting that apprenticeship programs:

  • eliminate onboarding costs (you get to teach a newbie developer your way of doing things)
  • eliminate recruiting costs (why pay a recruiter $10K when you can give it to your apprentice)
  • easier to hire seniors (who love to mentor)

Here's what you want to know about your plan:

  • How long will the program be? (suggestion: 3-4 months)
  • How many apprentices do you want to have? (ensure there are enough seniors to mentor them)
  • What should they know prior to starting? (do you expect them to have a basic working knowledge of Rails?)
  • What should they learn?
  • How will they learn it? (through tickets, a big project, pairing, etc)

For hiring your apprentices, you want an application (basic questions to get to the heart of what they are about), a code challenge, and a final interview. If possible, do end-to-end anonymization until they get to the final interview. You also want to ensure you have a rubric prior to starting this process. After you hire them, try giving lessons on foundation concepts, then give them small changes (bugs/internal code). Then rotate them around to different teams, including customer facing product. And don't forget to set early expectations!

Eileen's keynote

Eileen Uchitelle totally pumped me up. She discussed the various ways she is looking to make Rails more scalable by default. One of the things that really stuck with me was when she mentioned how so many companies are doing these things individually... so why not make them part of the overall framework and share the knowledge!

The Code Free Developer Interview

Can you tell I am into interviewing? This was a talk by Pete Holiday, also from CallRail. Here are the problems with coding during interviews:

  • don't replicate real work
  • disadvantage people without free time (code challenges)
  • live coding is very stressful, even for experienced people
  • difficult to develop and maintain a good code challenge
  • many passive candidates won't do the takehome (I've done this before)

So what's the solution? The primary solution is to just talk to candidates.

  • Ask all the candidates a consistent set of questions
  • Define a rubric ahead of time
  • Write down thoughts right after the interview

That's it! But there's more. Here are three possible techniques for a code-free interview:

1. Dig into their experience. Let them direct you to what they feel is most important. Ask questions like:

  • What was your role in the project?
  • How does the feature work?
  • What's the worst technical debt? Why hasn't the team fixed it? How would you fix it?
  • Has it had any bugs/outages in production? What happened? How did the team fix it?

2. Have them do a code review. If you choose this, make sure you are not using production code (they will have no context), are actively reducing complexity, and include realistic bugs without making it a bug hunt. One good option is to have a completely contrived situation with a simple application and a pull request to that simple app. Another is to fork an open source repository and create a contrived PR. The pull request should include no detail in the commit message, unsquashed commits, non-idiomatic code, overly complex, bad variable names, and actual bugs.

3. Try doing a collaborative system design. For this, you want to hypothetically build a tool, platform, or a project. You don't want any code or pseudocode and you should be working with the candidate. The general idea should be easy to understand and either related to the skills you're hiring for or well known. This can be forever-long, so it needs to be timeboxed. Let the candidate lead and build complexity if it's needed. For example:

Let's say we want to build Facebook. Get rid of the boilerplate (we already have users) and then ask "How do we implement status updates?". Once they get there, we can go deeper and ask about privacy controls, then granular privacy controls, and past that potential performance problems.

I loved this talk because I think code-free developer interviews should be the norm and have also been advocating for it at companies that I have been at.

Plays Well With Others: Improv For Nerds

H. Wade Minter gave this workshop and I don't have any notes on it because it was an improv class. But! One of the big things I took from it was our last activity. To remove bias from ideas, we did the following:

  • each wrote down an idea on how to improve RailsConf for next year
  • exchanged that idea with another person
  • each paired up with someone else, compared ideas, and gave each idea a number of points (total points for the two ideas could not be higher than 7)
  • exchanged ideas with a different person
  • wash, rinse, repeat until we have compared ideas 5 times

At that point, we had seen about 10 different ideas (plus our own) and the best idea could have a total score of 35 with the worst having a score of 0. Our top idea had a score of about 26, with a good number being around 22. We had a couple of bad ideas in the double digits (I'm looking at you, bacon table). This definitely seems like a good practice for any organization with a decent number of people.

And that's it...

I did some more, but I don't have any notes! I also sat and watched my friend Sam Phippen pair for an hour and a half, so if you want to learn yourself some RSpec, watch here!

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.

Polymorphic Routes

I just started classes (working toward the CS certificate at BU Met) and my new big project at work is porting over a ton of code from Rails 2 to Rails 4, so I’m sure I’m about to have tons to write about. For today, here’s something I somehow just found out about: polymorphic routes in Rails.

What are polymorphic routes? Let’s say you want to have a partial that is used for quite a few different models. Every model you have has a show page for individual instances of that model and each show page has an edit link. So instead of creating a new page for each, the view you have reads in a generic @object and then you can use polymorphic routes to generate the path for the edit link! In this example, I’ll have the @object represent an instance of the Article class. Like so:

edit_polymorphic_path(@object)

results in:

edit_article_path(@object)

I’m pretty surprised I haven’t seen this yet, but now I’m glad that I have! This is pretty cool :D

Rails Forms - Basic Errors

This is going to be short because I’ve been crazy busy, but nothing really worth blogging about. Two things about forms in Rails:

1) You can’t nest them within a table. Let’s say you have this bit of code in your ERB:

<tr>
  <%= form_for TestClass.new do |f| %>
    <td>
      <%= f.text_field :test_field %>
    </td>
    <td>
      <%= f.submit 'Create' %>
    </td> 
  <% end %>
</tr>

What this results in is this:

<tr>
  <form action='/test_class'></form>
  <td>
    <input name='test_class[test_field]' type='text'>
  </td>
  <td>
    <input value='Create' type='submit'>
  </td>
</tr>

The form ends up closing before it actually ends, which means that it won’t actually submit properly. Bizarrely enough, sometimes it works… but it won’t work consistently. Lesson: don’t nest forms in tables. Haven’t tried it, but there’s a possibility that with HTML5, if you define the form outside of the <table&gth; element, then it will work.

2) You cannot instantiate a form in one div and then end it in another. This may seem obvious, but I see this a lot, especially with juniors. Example:

<div>
  <%= form_for TestClass.new do |f| %>
    <%= f.text_field :test_field %>
</div>
<div>
  <%= f.text_field :num_ponies %>
  <%= f.submit 'Create' %>
<% end %>
</div>

Results in this:

<div>
  <form action='/test_class'></form>
  <input name='test_class[test_field]' type='text'>
</div>
<div>
  <input name='test_class[num_ponies]' type='text'>
  <input value='Create' type='submit'>
</div>

No bueno. Never nest anything across multiple elements. HTML does not appreciate it.

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 }}

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.

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.

Raising ActiveRecord::Rollback

I had a bit of code that I was running in a transaction block. Like so:

ActiveRecord::Base.transaction do
  @rating = Rating.new(rater: @user, rateable: @movie, score: params[:score])
  if !@rating.save
    Rails.logger.error("Error saving rating")
    raise ActiveRecord::Rollback
  end
end

I kept trying to test this by doing:

expect { post :create etc }.to raise_error ActiveRecord::Rollback

Total failure. Couldn’t figure out why for a while. Here’s the reason: raising ActiveRecord::Rollback just triggers a rollback of the transaction and does so silently. What I should’ve done is raise a different type of error (TBD) and that will still trigger a rollback of the transaction but the error will actually be raised and we can return an appropriate page to the user.

More info on StackOverflow.

Converting Seconds to Midnight Back to Time

Rails has a nice little function for it’s DateTime class called ‘seconds_since_midnight’. Super handy if you (for some reason) just want to store time, without a date in your database. However, here is the next problem you will come across: how do I display this value of seconds as an actual, human-readable time?

Luckily, this is simple:

def convert_to_time_from_second_to_midnight(seconds)
  hh, mm = seconds.divmod(60)[0].divmod(60)
  Time.parse('%d:%d' % [hh, mm]).strftime("%I:%M %p")
end

divmod is an interesting little function that I’ve never used prior to writing this.  It’s very simple: just returns the quotient and the modulus. It helps us get the hours and minutes within just one line. Then all we have to do is format it and the result is a nicely formatted time like so “12:30 PM”.

FactoryGirl vs. Bare Domain Objects

I’ve been doing a fair amount of research into testing and what are the best and fasted way to test. Recently, someone at work brought up the fact that using factories can drastically slow down your test suite. Especially when, like us, you create other factories within factories (ie. I need an advertiser for a campaign, so when I use Factory.build(:campaign), it also does Factory.create(:advertiser)). I tried using Factory.stub (check out this article), but it still would create in the background and I couldn’t change that without wrecking all of our other tests. I decided to start testing with bare domain objects, saving nothing to the database (when possible). Here is a comparison of using FactoryGirl vs. bare domain objects:

FactoryGirl

context '#validate flight dates with FactoryGirl' do
  setup do
    @campaign = FactoryGirl.build(:campaign, id: 1234, flight_start: Date.new(2014,01,28), flight_end: Date.new(2014,01,28))
  end
  
  should 'be valid if campaign has the same flight_start and flight_end' do
    assert_true @campaign.valid?
  end
  
  should 'be invalid if flight_end is before flight_start' do
    @campaign.flight_end = Date.new(2014,01,27)
    assert_false @campaign.valid?
    assert @campaign.errors.full_messages.include?('Flight start must be earlier than campaign flight end date')
  end
end

 

Bare Domain Objects

context '#validate flight dates with Bare Domain Objects' do
  should 'be valid if campaign has the same flight_start and flight_end' do
    campaign = Campaign.new(name: 'UNICORNS!!!', flight_start: Date.new(2014,01,28), flight_end: Date.new(2014,01,28))
    campaign.valid?
    assert_false campaign.errors.full_messages.include?('Flight start must be earlier than campaign flight end date')
  end
  
  should 'be invalid if flight_end is before flight_start' do
    campaign = Campaign.new(name: 'UNICORNS!!!', flight_start: Date.new(2014,01,28), flight_end: Date.new(2014,01,27))
    campaign.valid?
    assert campaign.errors.full_messages.include?('Flight start must be earlier than campaign flight end date')
  end
end

Not so different, right? Takes less time since I’m not writing to the database and the only major difference is that I don’t assert that the object is valid. Instead I check to see if it is adding my error message. This helps simplify tests, even for more complicated objects.

Test Unit + Shoulda

Do you use Thoughtbot’s shoulda matchers? If not you should. Super handy for testing Rails applications. However, I did ran into an issue that I banged my head on the wall for an hour before noticing the very simple problem.

I was adding some tests to a controller, and, in this case, they were already written but commented out, so I was just editing them and getting them to work. We use the shoulda matchers and the accompanying context blocks to organize our tests, but the tests I was editing were just using basic Test Unit syntax (test “this is what i’m testing” do). I kept getting an ArgumentError (1 for 2). However, the function I was testing only had one argument, so I couldn’t figure out why it was asking for a second. The issue ended up being that the tests were within a context block and context blocks do not work with Test Unit. Once I converted it to should “this is what I’m testing” do, it worked perfectly.

Simple mistake, but easy to miss if you are trying to fix up legacy code.

Testing Private Controller Methods in Rails

class FooController < ApplicationController
  private
  def is_awesome?(unicorns)
    unicorns.awesome == 'DEFINITELY'
  end
end

class FooControllerTest < ActionController::TestCase
  should 'return true when unicorns are awesome'
    @unicorn = Unicorn.new(awesome: 'DEFINITELY')
    assert_true @controller.send(:is_awesome?, @unicorn)
  end
end

Yesterday I wrote two private methods to a controller that I was working on and, failing on doing TDD, I wrote the tests today. I actually ran into a few issues because, originally, I was not passing anything to my methods. The methods were just taking instance variables that were defined in the new method that was calling my functions. Works in practice, but when I wanted to test just these methods, I could not get them to read the instance variable without calling the whole new method. End result after trying a few other things? Just passing the variable to the private method (as seen above). Still works when called in the new method, but is also much easier to test. Success!