Kamil Baćkowski
Foreign key constraints
create_table :posts do |t|
t.references :user, foreign_key: true, index: true, null: false
end
Not null constraints
create_table :comments do |t|
t.text :body, null: false
t.boolean :published, null: false, default: false # usually we should add not null for boolean fields
end
How to properly fix “undefined method `email’ for nil:NilClass”
Problem :
<div>@comment.user.email</div>
Fix :
# migrate the invalid data by either removing comments without user or try to assign comments to i.e. post owner
# ...
change_column_null :comments, :user_id, false
class User < ApplicationRecord
enum status: [:pending, :approved]
end
Use instead :
class User < ApplicationRecord
enum status: { pending: 'pending', approved: 'approved' }
end
class User < ApplicationRecord
class << self
%w(pending approved).each do |type|
define_method("#{type}_only") do
where(status: type)
end
end
end
end
vs
class User < ApplicationRecord
def self.pending_only
where(status: :pending)
end
def self.approved_only
where(status: :approved)
end
end
class Company < ApplicationRecord
belongs_to :user
has_many :roles
has_one :setting
has_one :subscription
after_create :create_roles
after_create :create_subscription!
after_create :create_setting!
def create_roles
roles.create! user: user, name: :owner
end
end
vs
class CreateCompany
def initialize(user)
@user = user
end
def call
Company.create!(user: @user).tap do |company|
company.create_setting!
company.create_subscription!
company.roles.create! user: @user, name: :owner
end
end
end
However callbacks should be used as long as they don’t touch other records or business logic
class Company < ApplicationRecord
before_create :generate_unique_token
before_save :strip_name
def strip_name
self.name = name.strip
end
def generate_unique_token
self.token = SecureRandom.hex
end
end
def create
current_user.create_company!.tap do |company|
company.create_setting!
company.create_subscription!
company.roles.create! user: current_user, name: :owner # this raises validation error
end
end
Result will be a company without role.
Processing by CompaniesController#create as HTML
(0.1ms) begin transaction
SQL (0.9ms) INSERT INTO "companies" ("user_id") VALUES (?) [["user_id", 8]]
(118.3ms) commit transaction
Company Load (0.1ms) SELECT "companies".* FROM "companies" WHERE "companies"."user_id" = ? LIMIT ? [["user_id", 8], ["LIMIT", 1]]
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "settings" ("company_id") VALUES (?) [["company_id", 16]]
(127.3ms) commit transaction
Setting Load (0.2ms) SELECT "settings".* FROM "settings" WHERE "settings"."company_id" = ? LIMIT ? [["company_id", 16], ["LIMIT", 1]]
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "subscriptions" ("company_id") VALUES (?) [["company_id", 16]]
(112.5ms) commit transaction
Subscription Load (0.3ms) SELECT "subscriptions".* FROM "subscriptions" WHERE "subscriptions"."company_id" = ? LIMIT ? [["company_id", 16], ["LIMIT", 1]]
(0.1ms) begin transaction
(0.1ms) rollback transaction
def create
Company.transaction do
current_user.create_company!.tap do |company|
company.create_setting!
company.create_subscription!
company.roles.create! user: current_user, name: :owner # this raises validation error
end
end
end
Or even better:
around_action :with_transaction
def create
current_user.create_company!.tap do |company|
company.create_setting!
company.create_subscription!
company.roles.create! user: current_user, name: :owner # this raises validation error
end
end
private
def with_transaction(&block)
ActiveRecord::Base.transaction(&block)
end
Do not require gem by default if it’s not needed
gem 'twilio', require: false
# file: lib/send_sms.rb
require 'twilio'
class SendSms
def self.call(phone_number:, body:)
#send sms using twilio gem
end
end
Think twice before adding gem which functionality can be easily implemented from scratch
Example of gems that should be replaced by own implementation:
Name variables according to their type & context:
post
# vs
post_body # because we have string inside
pages
# vs
pages_count # because we have number inside
comment_id
# vs
comment_ids # because we have array inside
user.comments.map(&:id)
# vs
user.comment_ids
comment.user.id
# vs
comment.user_id
user.comments.map { |c| c.body }
# vs
user.comments.map(&:body)
user_admin = UserAdmin.first
# vs
user_admin = UserAdmin.first!
# file: app/assets/javascripts/calendar.coffee
window.Calendar = {
initialize: ->
@_initializeDateInputs()
@_initializeNavigationActions()
_initializeDateInputs: ->
@('#calendar .date-inputs').datepicker()
...
_initializeNavigationActions: ->
...
# file: app/assets/javascripts/application.js
//= require calendar
<div id="calendar">
...
</div>
:coffescript
Calendar.initialize()
$('#sign_up_form').on 'submit', ->
...
Create simple exists() function and use it:
$.fn.exists = ->
if !@length
throw new Error("No elements matched by " + @selector)
return @
$('#sign_up_form').exists().on 'submit', ->
...
Questions ?