Slide 1

Slide 1 text

ActiveRecord basics and Postgres features Dax Huiberts CTO/developer@ Salonized.com

Slide 2

Slide 2 text

Why postgres • It's a great tool • It gets the job done • I like having just one data source • It can handle a lot of data (+100M records) • Guarantee data consistency • It is much faster than your ruby code

Slide 3

Slide 3 text

Things I don't like • Caching • Inconsistent data • Silent failures

Slide 4

Slide 4 text

Basics

Slide 5

Slide 5 text

save vs save!

Slide 6

Slide 6 text

@user = User.new(params[:user]) if @user.save redirect_to user_path(@user) else render :new end

Slide 7

Slide 7 text

csv_data.each do |line| @user = User.new(line.to_h) @user.save! end

Slide 8

Slide 8 text

ActiveRecord::Base.transaction do csv_data.each do |line| @user = User.new(line.to_h) @user.save! end end

Slide 9

Slide 9 text

Uniqueness validations

Slide 10

Slide 10 text

class User < ActiveRecord::Base validates :email, uniqueness: true end

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

class EmailUniqIndex < ActiveRecord::Migration def up add_index :users, :email, unique: true end end

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Associations

Slide 15

Slide 15 text

class Invoice < ActiveRecord::Base has_many :line_items end class LineItem < ActiveRecord::Base belongs_to :invoice validates :invoice, presence: true end

Slide 16

Slide 16 text

class Invoice < ActiveRecord::Base has_many :line_items, dependent: :destroy end class LineItem < ActiveRecord::Base belongs_to :invoice validates :invoice, presence: true end

Slide 17

Slide 17 text

class AddInvoiceIdToLineItems < ActiveRecord::Migration def up add_reference :line_items, :invoice, foreign_key: true, index: true, null: false end end

Slide 18

Slide 18 text

N+1 queries

Slide 19

Slide 19 text

Invoice.limit(100).each do |invoice| invoice.line_items.each do |line_item| # do something with line_item end end

Slide 20

Slide 20 text

invoices = Invoice.limit(100).includes(:line_items) invoices.each do |invoice| invoice.line_items.each do |line_item| # do something with line_item end end

Slide 21

Slide 21 text

Missing indexes

Slide 22

Slide 22 text

invoices = Invoice.limit(100).includes(:line_items) invoices.each do |invoice| invoice.line_items.each do |line_item| # do something with line_item end end

Slide 23

Slide 23 text

Advanced stuff

Slide 24

Slide 24 text

Polymorphic associations which are evil

Slide 25

Slide 25 text

class LineItem < ActiveRecord::Base belongs_to :invoice belongs_to :invoicable, polymorphic: true validates :invoice, presence: true validates :invoicable, presence: true end

Slide 26

Slide 26 text

class LineItem < ActiveRecord::Base belongs_to :invoice belongs_to :product belongs_to :service validates :invoice, presence: true validates :product, presence: true, unless: :service? validates :product, absence: true, if: :service? validates :service, presence: true, unless: :product? validates :service, absence: true, if: :product? end

Slide 27

Slide 27 text

class LineItem < ActiveRecord::Base belongs_to :invoice belongs_to :product belongs_to :service validates :invoice, presence: true validates :kind, inclusion: {in: ['product', 'service']} validates :product, presence: true, if: "kind == 'product'" validates :product, absence: true, if: "kind != 'product'" validates :service, presence: true, if: "kind == 'service'" validates :service, absence: true, if: "kind != 'service'" end

Slide 28

Slide 28 text

ALTER TABLE line_items ADD CONSTRAINT kind_values CHECK (kind IN ('product', 'service'); ALTER TABLE line_items ADD CONSTRAINT product_kind CHECK ( (kind = 'product' AND product_id IS NOT NULL) OR (kind != 'product' AND product_id IS NULL) ); ALTER TABLE line_items ADD CONSTRAINT service_kind CHECK ( (kind = 'service' AND service_id IS NOT NULL) OR (kind != 'service' AND service_id IS NULL) );

Slide 29

Slide 29 text

Database views as rails models

Slide 30

Slide 30 text

Search over multiple tables

Slide 31

Slide 31 text

CREATE VIEW invoicables AS SELECT concat('p', p.id) AS id, 'product' AS kind, p.id AS product_id, NULL AS service_id, p.name, p.price FROM products AS p UNION ALL SELECT concat('s', s.id) AS id, 'service' AS kind, NULL AS product_id, s.id AS service_id, s.name, s.price FROM services AS s;

Slide 32

Slide 32 text

class Invoicable < ActiveRecord::Base self.primary_key = :id belongs_to :product belongs_to :service # attribute :kind # attribute :name # attribute :price end

Slide 33

Slide 33 text

Aggregated results for reporting

Slide 34

Slide 34 text

CREATE VIEW derived_invoices AS SELECT i.id AS id, i.id AS invoice_id, COUNT(li.id) AS item_count, SUM(li.price * li.quantity) AS total_price FROM invoices AS i LEFT JOIN line_items AS li ON i.id = li.invoice_id GROUP BY i.id;

Slide 35

Slide 35 text

class DerivedInvoice < ActiveRecord::Base belongs_to :invoice # attribute :item_count # attribute :total_price end

Slide 36

Slide 36 text

One last trick just for showing off

Slide 37

Slide 37 text

class RosterEntry < ActiveRecord::Base # attribute :start_date # attribute :end_date end

Slide 38

Slide 38 text

CREATE VIEW roster_days AS SELECT UNNEST(ARRAY( SELECT next_date FROM GENERATE_SERIES( start_date, end_date, '1 week' ) AS next_date ))::date AS date FROM roster_entries

Slide 39

Slide 39 text

Learnings • Breathe SQL • Use The Index, Luke • Look out for slow queries • `explain analyze` is your friend • Postgres is awesome!

Slide 40

Slide 40 text

Thank you PS: We're hiring! :-)