If you've seen any of my other posts, you might know that I'm doing the full-time Software Engineering (really Web Dev) bootcamp at Flatiron School. We've been building up to Rails for a couple months now, and we finally got our first crack at building a Rails app completely on our own. For my final project I chose to build a site for tracking allergies and dietary restrictions in schools (for use by teachers taking field trips, cafeterias with students that have nut allergies, etc.). If you're interested, or have any advice, feel free to check that out here.
So let's get into it. These are four things I would go back and tell my past self to save a lot of time and headache building my first project with Rails. I won't claim to have the perfect list for the most common issues, but hopefully I can at least help y'all avoid some frustration in these particular areas.
There are specific sandbox services or full virtual environments that programmers use, particularly for security reasons, but all I mean by a sandbox here is a place to play around and test code. That can be as simple as creating a second Rails app with the same structure as your main project.
This is especially useful for sorting out the associations between models and testing out database seeds. If you're starting with only a few basic has-many/belongs-to relationships, maybe it's straightforward enough to skip a test project, but as the relationships get more and more complex, I can't recommend a tester pancake enough. When I haven't used a sandbox in the past, I've ended up accidentally committing a bunch of changes back and forth in the beginning of my project, thinking I had it all figured out before I did. It helps keep your main project code way cleaner by replicating only what worked and was well-tested in the sandbox, and it gives you an isolated, identically-structured database to go back to if you want to try something out as the project progresses.
In any many-to-many relationship, you'll need a join table (more on ActiveRecord Associations here). But why is this
class Assignment < ActiveRecord::Base belongs_to :programmer # foreign key - programmer_id belongs_to :project # foreign key - project_id end class Programmer < ActiveRecord::Base has_many :assignments has_many :projects, through: :assignments end class Project < ActiveRecord::Base has_many :assignments has_many :programmers, through: :assignments end
better than this?
class Programmer < ActiveRecord::Base has_and_belongs_to_many :projects # foreign keys in the join table end class Project < ActiveRecord::Base has_and_belongs_to_many :programmers # foreign keys in the join table end
What gives? I thought as programmers we were supposed to value our time and always go for the easiest, most concise code possible? Yes, while the second option is cleaner looking and takes half the code, it's also significantly less flexible (and speaking as a beginner in Rails, potentially a lot more confusing).
First off, it doesn't allow us to work with the join table in any way. Since we don't have a model for it, we don't have the option to add any attributes or validations to it later if we want. And maybe that's just fine for your use case. Using
has_and_belongs_to_many and creating the corresponding join table in a migration is a perfectly legitimate option, but for beginners I'd recommend starting with
has_many_through. Experienced programmers probably will have a better idea of their end product when they're setting everything up initially, but for me at this stage it's still pretty common to think halfway through a project "Oh, of course I should have added 'x' attribute to my join table". Starting my projects with
has_many_through relationships gives me the flexibility to just add a single migration with the attribute I missed, rather than rolling back my entire database and changing all the models.
Ok, so if we're going to associate our models with
has_many_through why not just use a standard join table name (for example,
programmers_projects? Again, you totally can, it's just not as clear. Rails is great; it has so many things built into it that do magic for programmers and make our lives easier. But that Rails magic relies a lot on, among other things, naming conventions. Like many parts of the framework, Rails expects join tables' names and filenames to have a particular word order and pluralization. I would go back and forth a lot between
projects_programmers, etc. trying to figure out the problem before I finally just named my join table something else entirely.
Ok, so I know the very first thing I said was 'Use A Sandbox', but just like with anything else, there's a limit. Having a test project to get started cleanly and try stuff out is one thing, but depending on it so much that every time you make a change you try it there first is obviously too much. It doesn't even have to be that extreme.
For example, I wanted my users to be able to log in through Google, but I was having trouble getting Omniauth to work properly. Maybe it was interacting weirdly with some other part of my code and having a more simplified set up that looks the same would make it easier to spot the bad interaction, or maybe I just needed to start fresh because I missed something in the setup. Either way, perfect candidate for the sandbox! But even when I got it figured out there, I still couldn't get it to transfer over to my real project...
I spent way too long trolling Google and Stack Overflow for answers about what could be interfering with Omniauth (and commenting out different parts of my code), only to find that I had copied all but one gem into my real project's Gemfile from the sandbox. And Omniauth was working just fine!
WOW! -- SUPER IRRITATING!
As programmers, we want to keep our code as DRY (Don't Repeat Yourself) as possible, and the same principle applies here. It's not just about efficiency (although typing every new thing into a sandbox before you add it to your project does eat up a lot of extra time); it's mostly about protecting yourself from stupid mistakes like I made. The more times you type the same code in different places, the more likely you are to overlook something or make a mistake. So be careful with your sandboxes!
Rails has a ton of crazy useful tools to make programming faster and easier. Trust me, I spent a lot of time during my project on this page trying to leverage the power of Form Helpers. But sometimes there can be a little too much Rails magic. If you're a student like me, you probably learned and practiced a lot with these helper methods and not a lot with building out similar things yourself. Of course, why would we do it the slow, hard way when there's a fast, easy way?? Well, those helpers are made to solve the most common problems, so what happens when you run into an uncommon problem?
My advice while you're learning, especially with form helpers but across the board, is to get as comfortable as possible throwing out the magic and learning how the common stuff works behind the scenes a bit more. I ended up spending a lot of time searching for syntax solutions to try to make a more complex set of associations work with form helpers in the way I learned them, when really they were problems that could probably have been solved a different way with 10-15min of creative thinking.
It's really tempting to always go for the most abstract option ("How do I simplify this as much as possible and let Rails magic do the work for me"), but my experience so far has been that just because it's the 'easiest' solution or a really common problem that Rails has built in a method for, doesn't mean it's the right solution for your project (or that it'll save you any time). This project is the most creative and adaptable that I've had to be as a programmer, and I was most successful when I was willing to have it look ugly for a while and just make it work. Code that works is better than code that looks clean and concise but doesn't work. We'll always look back on old code and be embarrassed about how we used to do things, but the shortcuts are easier to use correctly if you spend time on the fundamentals.
That's probably enough unsolicited advice for now. If anyone wants to check out my janky code to see if you should even listen to a word I say, the link to the repo is up at the top (also, feel free to watch the walk through video). Thanks for reading, and happy coding!