🛤
some Rails tips
on programminglast updated 7/23/21
batch processing
#find_each
is an Active Record method for efficiently handling big collections of database records.
This is a simplistic comparison, but something like Customer.all.each...
would fetch an entire table all at once whereas Customer.find_each...
would process records in batches. The Rails docs explain much better than me.
When we're certain the collection returned will be smaller than 1,000 records, #all
works just fine. Otherwise, and especially with production data (!), use #find_each
.
# for each customer with an :email attribute present, set :emailable to true
# efficient 👍
Customer.find_each do |customer|
customer.update!(emailable: true) if customer.email.present?
end
# relatively inefficient 👎
Customer.all.each do |customer|
customer.update!(emailable: true) if customer.email.present?
end
writing decent migrations
Running a Rails migration to add a column that already exists can trigger an error:
# when the customers table already has a birthday column
class AddBirthdayToCustomers < ActiveRecord::Migration[5.2]
def change
add_column :customers, :birthday, :date
end
end
rake db:migrate #=>
# StandardError: An error has occurred...
# PG::DuplicateColumn: ERROR: column...
Instead, we can write something like this to check for pre-existing columns and avoid DuplicateColumn
errors:
class AddBirthdayToCustomers < ActiveRecord::Migration[5.2]
def change
unless column_exists?(:customers, :birthday)
add_column :customers, :birthday, :date
end
end
end
# and with Rails 6.1, we have if_exists / if_not_exists
class AddBirthdayToCustomers < ActiveRecord::Migration[6.1]
def change
add_column :customers, :birthday, :date, if_not_exists: true
end
end
There's a decent argument to avoid using .reset_column_information
— shoutout this guy I don't know for making a thoughtful case — but as the Rails API docs explain, it can be useful for populating new columns or tables with default values.
As an example, the acts_as_list gem is handy for implementing sorting functionality. Adding this functionality to existing models requires us to set default position values:
# form definitions have many questions, a question belongs to a form definition
# we want to allow sorting questions for form definitions
class AddPositionToFormQuestions < ActiveRecord::Migration[6.1]
def change
unless column_exists?(:form_questions, :position)
add_column :form_questions, :position, :integer
# after adding a :position column to the form_questions table
# we ensure the table is reloaded on our next request and :position exists...
Form::Question.reset_column_information
# ...so we can set a default :position value for each form question
Form::Definition.find_each do |form_definition|
form_definition.questions.order(:updated_at).each.with_index(1) do |question, index|
question.update_column :position, index
end
end
end
end
end