I dove back into a Ruby/Rails project I started about a month ago after reading a great book on Rails. It got me really excited to jump in, but I quickly lost myself the moment I wanted to bend away from the tutorials. The project I was working on today was a basic management tool for helping me manage various web services, the providers they live on, the projects they are attached to, and other similar connections.
I thought I’d take a moment to reflect on the issues that caused me the most pain.
Getting a select-option dropdown to populate dynamically
I have a 1:many relationship with some of my tables.
class Provider << ActiveRecord::Base has_many :services end class Service << ActiveRecord::Base belongs_to :provider end
By default, after scaffolding, the view for
Service includes a text field seeking an integer for
provider_id with no context given to what you’re picking. I wanted to create a dropdown in the view that was populated dynamically from the existing Providers.
There appear to be two main ways to accomplish this (and there’s likely many more).
- Create an instance variable in your controller for the collection of Providers, which you can reference in the view.
- Grab the list in the view.
There are many tag helpers for creating these dropdowns for you, all with their own sets of options. My original goal was to try option 1, creating an instance var in the controller, and sending it to the view like a good, separated app seemingly should do.
The Controller, as it was staring me down:
class ServiceController < ApplicationController #... stuff def new @service = Service.new end def create @service = Service.new(service_params) respond_to do |format| # stuff... end end
I struggled for an embarrassingly long time because I wasn’t creating the instance var in the correct action method. I had tried
@providers = Provider.select(#stuff) but was placing it only in
def create. After adding the relevant tag helper to the view, I couldn’t figure out for the life of me why that variable wasn’t getting sent to the view as expected.
Note to self (and dangit, I knew this too): The view being presented to let the user create a new object is the
#new action. Which is completely separate from the
#create action, which is just an API to allow you to save the model, and figure out what to do next. Once I put the code into the correct method, all was well. I can’t believe how long it took me to discover.
The alternative way someone had shown me is to place the following code directly into the view:
<%= f.select :provider_id, Provider.order(name: :asc).pluck(:name, :id) %>
I’ve gone back and forth as to whether this is okay to have in this context. My instinct says “Hey hey hey! This is too much codestuffs in the view! Put that elsewhere!” but in reality, a similar amount of code has to get put into the view regardless. The difference is that in method 1, you are creating your collection in the controller and passing it to the view (perhaps allowing for more comfortable edits and filtering later on?). In method 2, the collection is being generated in the view. In the end, I guess I decided that it was actually easier to maintain like this, even though it feels a little wrong. The code is now in one distinct spot, and really, the entire function of this logic is explicitly to get the options for a dropdown. It’s still succinct and obvious what is happening.
Next up: What the heck is
select are mates. These two methods achieve very similar results, but in very different ways. In short,
pluck gives you a sort of shortcut to attributes in your table, returning only an array of what values it found during the query, where
select actually creates objects for each entity first, from which you can pull attributes. Gavin Miller gives a good writeup about the difference, including performance testing. His big takeaway was:
pluck for model values, select for model objects
Thanks Gavin, you taught me some cool stuff today!
They both have their places, but today’s place is for
Changing a column_name in a model needs to get whitelisted!
I decided that I wanted to change
Service.address. What was I thinking when I created that the first time! The worst offender of frustration in this chapter came from Strong Parameters in my ServiceController. When I changed
address I only tested the views, and saw to it that my schema.rb was updated accordingly. Everything looked good and I moved on. Suddenly I started noticing problems where my IP addresses were not getting saved when submitting through
#create. They saved to the .db fine when I seeded data, or interacted with irb. Unfortunately this took me a long time to debug. The error isn’t thrown as a real error – it’s just a tiny little line hidden amongst a rapid rush of other log information related to db queries and page requests. It said something like “Parameter not accepted” or something. In short, I also needed to modify the new column name in my controller under
params.permit. Back in business!
Git Push … Oh shoot, ammend… oh god no what have I done.
I’ll admit, I’m a total git noob right now. I have a hard goal of sorting it, but I’m not there yet. Because of this, when things go awry – I usually cry and re-clone or re-fork. I know this is ridiculous, but I’m not yet comfortable with digging out of the gitpickle.
So I’m working on my project when I realize I made a commit and pushed it a step too early. Okay, no biggie. Fix the dealy. Ammend my last commit. Local is now out of sync with Origin. Sweeeeeet. Uhmmm…
git pull? Good afternoon, conflict markers! How rude! Git just puked all over my files! I only have experience with nice auto-merges so far. Thankfully, in the end, all it took was some manual editing: deleting the git puke from the conflicts, and choosing what I want to use. Then I just had to run a simple
git commit and boom - I was back to 2 steps ahead of Origin – ready to push up like a Flintstone pop.