Code & Clay Notes to self. Mainly Ruby/Rails.

How to rename database columns in Rails

Often when I begin a project, I find myself rolling back migrations, dropping the database, renaming tables and columns whilst I figure out the shape of the model. If I need to rename a column, I’ll just update or delete the corresponding migration.

However, it is possible to change a column’s name in a migration.

Say, I have a table tasks and I want to change the name of the title column to name. First I ask Rails to generate the migration:

rails generate migration rename_title_to_name_in_tasks

The generated migration looks like this:

class RenameTitleToNameInTasks < ActiveRecord::Migration[6.0]
  def change
  end
end

I then remove change and add the up and down migration like so:

class RenameTitleToNameInTasks < ActiveRecord::Migration[6.0]
  def up
    rename_column :tasks, :title, :name
  end

  def down
    rename_column :tasks, :name, :title
  end
end

rename_column takes three arguments. The first is the table name. The second is the existing column name. The third is the new name.

Above, I have defined the up and down migration. That is, the up migration does the renaming bit when we migrate the database. The down migration runs when we roll back the migration – in this instance, it reverts the column’s name back to the original.

Before I run the migration, I give the schema a quick check.

  create_table "tasks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "title", null: false
    t.text "notes", default: ""
    t.uuid "task_list_id"
    t.uuid "fulfiller_id"
    t.datetime "completed_at", precision: 6
    t.index ["fulfiller_id"], name: "index_tasks_on_fulfiller_id"
    t.index ["task_list_id"], name: "index_tasks_on_task_list_id"
  end

Then, after running rails db:migrate, I check it again to see if the migration has worked:

  create_table "tasks", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "name", null: false
    t.text "notes", default: ""
    t.uuid "task_list_id"
    t.uuid "fulfiller_id"
    t.datetime "completed_at", precision: 6
    t.index ["fulfiller_id"], name: "index_tasks_on_fulfiller_id"
    t.index ["task_list_id"], name: "index_tasks_on_task_list_id"
  end

All done!

Of course, five minutes later, I might decide that the new name probably isn’t the best and I can run rails db:rollback. On doing so, I can see that the column name has reverted to title.

Use inverse_of when creating associations with non-standard naming

I have two models ‘Customer’ and ‘GreenPencil’.

A customer can have many green pencils. But, as far as the customer is concerned, these are just pencils. It makes sense for me to call the relationship that.

I first establish that a green pencil belongs to a customer.

class GreenPencil < ApplicationRecord
  belongs_to :customer, required: true
  #...

Now, when I establish the other side of the relationship (remember, each GreenPencil has a foreign key pointing to its Customer) I can do this:

class Customer < ApplicationRecord
  has_many :pencils,
           class_name: 'GreenPencil',
           inverse_of: :customer

I’m telling Rails that a Customer has many pencils, the associated class is GreenPencil and that this relationship is the other side of GreenPencil’s relationship with a customer that I established at the start.

Rails usually sets inverse_of on all associations itself. However, because the class name differs from the association name, we need to specifiy it explicitly.

Rails will infer the inverse relationship if we set foreign_key but I think inverse_of is clearer to the reader.

Nested attributes

Sometimes it’s desirable to update one model’s attributes through an associated parent. Say, I might provide a form that asks a user for their name and email address but the user’s name is stored in the users table and their email address is recorded in another.

Rails provides the accepts_nested_attributes_for macro.

class User < ApplicationRecord
    has_one :contact_information, dependent: :destroy, required: true
    accepts_nested_attributes_for :contact_information

This allows me to update a user’s contact information like so:

user.update_attributes( contact_information_attributes: {
    phone_number: '123',
    email_address: 'hey@you.com
})

Instead of the headache of multiple forms and multiple controllers, I can now nest the contact information fields within the user form.

<%= bootstrap_form_for(@user) do |f| %>
  <%= f.text_field :name, required: true, autocomplete: "name" %>
  # ...
  <%= f.fields_for :contact_information do |ff| %>
      <%= ff.text_field :email_address, %>
      # ...

Finally, I need to specify the associated model’s attributes as allowable parameters in the parent resource’s controller:

def strong_params
    params.require(:user).permit(
      :name,
      contact_information_attributes: %i[
        email_address
      ]
    )
end

Use is_a? when checking class

Do not use == when checking that an object is of a given class.

Whilst it works here:

> Array.new.class == Array
=> true

…it won’t work with descendants of the class we’re checking against:

> class Thing < Array; end
=> nil
> Thing.new.class
=> Thing
> Thing.new.class == Array
=> false

Instead use is_a?:

> Array.new.is_a? Array
=> true
> Thing.new.is_a? Thing
=> true
> Thing.new.is_a? Array
=> true

Enhanced shell scripting with Ruby

From Dev_Dungeon:

Ruby is a better Perl and in my opinion is an essential language for system administrators. If you are still writing scripts in Bash, I hope this inspires you to start integrating Ruby in to your shell scripts. I will show you how you can ease in to it and make the transition very smoothly.

I’ve learnt a few things from the article:

Debug output, and anything that does not belong in the output of the application should go to STDERR and only data that should be piped to anothe application or stored should go to STDOUT

There’s ARGF in addition to ARGV.

I can ask the user for a password without the password being echoed back to the terminal:

#!/usr/bin/ruby
require 'io/console'

# The prompt is optional
password = IO::console.getpass "Enter Password: "
puts "Your password was #{password.length} characters long."

I can check for return codes and errors via the $? object.

I can trap interrupt signals with Signal.trap().

#!/usr/bin/ruby

Signal.trap("SIGINT") {
    puts 'Caught a CTRL-C / SIGINT. Shutting down cleanly.'
    exit(true)
}

puts 'Running forever until CTRL-C / SIGINT signal is recieved.'
while true do end