My First Rails App: Lessons Learned

This past week me and Tim built a Rails app called ConcertCurator. Basically this app lists a bunch of upcoming concerts in NYC and has some nice ways to browse through them. This is my first time working on a Rails app from scratch, and I thought I’d blog about two challenges we ran into along the way, and the workarounds we found.

Challenge 1: Find Similar Show

One challenge was getting this button to work:

Similar Show Button

The idea here is to take an event, and if the event has genres associated with it, find another show that has at least one of those generes associated with it too.

The logic isn’t super complex here, and we were able to get some code working before too long. But even though the button worked, it was extremely slow. Here’s that first pass.

1
2
3
4
5
6
7
8
# Inside the Event model
def self.find_similar_shows(genre_ids)
  self.select do |event|
    event.artists.any? do |artist|
      genre_ids & artist.genres.pluck(:id) != []
    end
  end
end

This method is inside the Event model. It works something like this:

  • Iterate through all the events
  • For each event, look through all the associated artists
  • Finally, for each artist see if it has any genres in common with the ones we passed in
  • If so, add that to the selection of events that the method returns

The logic is sound here, and the code works, but it’s SOOOO slow because it’s making SOOOO many queries to the database. These nested iterations are really costly.

Here’s the refactored code. This new version is much faster:

1
2
3
4
# Inside the Event model
def self.find_similar_shows(genre_id)
  self.joins(:artists => :genres).where(genres: {id: genre_id})
end

This version uses joins to join the genres table to the events table through the artists table, then uses where to make a single query to that giant joined table. This version really cooperates with SQL much better and uses its strengths. Instead of querying the database a ton of times with little simple queries, we just make a single, complex query to the database and let SQL do more of the heavy lifting. This spits out a response muuuuch much faster than the original version.

Challenge 2: Loading a Map

On each page that shows an event or a venue, our app uses the Google Maps API to show the location of the venue.

A Map on an Event Page

We didn’t do anything super fancy here–we had each venue’s longitude and latitude from another API so we just had to plug that in and render a map.

The tricky part was getting the map to actually show up on the page. At first we had a bug where, when we first navigated to a page, no map would show, but then after manually refreshing it would pop up. What gives?

Here’s our original code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function initialize() {
  var myLatlng = //grab lat-long of current venue
  var my_venue = //grab the venue name
  var mapOptions = {
    //map options here
  };
  var map = new google.maps.Map(document.getElementById('map-canvas'),
      mapOptions);
  var marker = new google.maps.Marker({
  position: myLatlng,
  map: map,
  title: my_venue
  });
}
google.maps.event.addDomListener(window, 'load', initialize);

Basically, this code sets up some variabales we need for the map inside an initialize function. Then, the last line listens for the window load event, at which point it calls initialize and renders the map. So why did we have to refresh the page to trigger this? Why didn’t the window load event trigger our function every time we navigated to the page?

The answer has to do with Turbolinks!!!

Turbolinks

Turbolinks are this feature built into Rails that loads the page without “really” “loading” the “page”. When you click on a link, it uses an AJAX call to just swap in the new body of the page, rather than loading the whole page from scratch. This makes clicking around faster, which is all well and good, but it doesn’t trigger the window ‘load’ event that our code is patiently listening for.

So here’s the fix–just need to add one more line of code:

1
2
google.maps.event.addDomListener(window, 'load', initialize);
google.maps.event.addDomListener(window, 'page:load', initialize);

Now, on top of listening to the normal window load event, we’re also listening for the page:load event. This is a special event that Turbolinks creates–since it’s bypassing a normal load, Turbolinks gives us this alternate way for us to hook into this part of the cycle and trigger our initialize function. See here for more documentation on Turbolinks.

Conclusion

Building this app was a lot of fun! Even some of the tricker parts were satisfying to work through. These two challenges I just walked through I thought were particularly interesting, and definitely taught me some handy lessons. Hope you agree!