Slide 1

Slide 1 text

Refinements in Ruby twitter.com/kddeisz

Slide 2

Slide 2 text

twitter.com/kddeisz Refinements in Ruby class C def foo puts "C#foo" end end

Slide 3

Slide 3 text

twitter.com/kddeisz Refinements in Ruby class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end

Slide 4

Slide 4 text

twitter.com/kddeisz Refinements in Ruby class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end

Slide 5

Slide 5 text

twitter.com/kddeisz Refinements in Ruby class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end

Slide 6

Slide 6 text

twitter.com/kddeisz Refinements in Ruby class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end using M c = C.new

Slide 7

Slide 7 text

twitter.com/kddeisz Refinements in Ruby class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end using M c = C.new

Slide 8

Slide 8 text

twitter.com/kddeisz Refinements in Ruby class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end using M c = C.new

Slide 9

Slide 9 text

twitter.com/kddeisz Refinements in Ruby class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end using M c = C.new c.foo # prints "C#foo in M"

Slide 10

Slide 10 text

Usage

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Law of Demeter

Slide 13

Slide 13 text

Law of Demeter Common interfaces

Slide 14

Slide 14 text

Law of Demeter Common interfaces Local monkey-patches

Slide 15

Slide 15 text

twitter.com/kddeisz Refinements in Ruby class Organization < ApplicationRecord has_many :events has_many :users end class Event < ApplicationRecord belongs_to :host, class_name: 'User' belongs_to :organization has_many :rsvps end class Rsvp < ApplicationRecord belongs_to :event belongs_to :user end class User < ApplicationRecord belongs_to :organization has_many :hosted_events, foreign_key: :host_id, class_name: 'Event' has_many :rsvps end

Slide 16

Slide 16 text

Law of Demeter

Slide 17

Slide 17 text

Law of Demeter Only talk to your friends

Slide 18

Slide 18 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate :allowed_by_event? private def allowed_by_event? !event.invite_only? || rsvp.status_was == 'invited' end end class Event < ApplicationRecord has_many :rsvps end

Slide 19

Slide 19 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate :allowed_by_event? private def allowed_by_event? (!event.invite_only? || rsvp.status_was == 'invited') && (event.cap? && event.rsvps.size <= event.cap - 1) end end class Event < ApplicationRecord has_many :rsvps end

Slide 20

Slide 20 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate :allowed_by_event? private def allowed_by_event? (!event.invite_only? || rsvp.status_was == 'invited') && (event.cap? && event.rsvps.size <= event.cap - 1) end end class Event < ApplicationRecord has_many :rsvps end

Slide 21

Slide 21 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate :allowed_by_event? private def allowed_by_event? (!event.invite_only? || rsvp.status_was == 'invited') && (event.cap? && event.rsvps.size <= event.cap - 1) end end class Event < ApplicationRecord has_many :rsvps end

Slide 22

Slide 22 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate :allowed_by_event? private def allowed_by_event? (!event.invite_only? || rsvp.status_was == 'invited') && (event.cap? && event.rsvps.size <= event.cap - 1) end end class Event < ApplicationRecord has_many :rsvps end

Slide 23

Slide 23 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate :allowed_by_event? private def allowed_by_event? event.allowed_rsvp?(status_was) end end class Event < ApplicationRecord has_many :rsvps def allowed_rsvp?(status_was) (!invite_only? || status_was == 'invited') && (cap? && rsvps.size <= cap - 1) end end

Slide 24

Slide 24 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate { event.allowed_rsvp?(status_was) } end class Event < ApplicationRecord has_many :rsvps def allowed_rsvp?(status_was) (!invite_only? || status_was == 'invited') && (cap? && rsvps.size <= cap - 1) end end

Slide 25

Slide 25 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord using Module.new { refine Event do def allowed_rsvp?(status_was) (!invite_only? || status_was == 'invited') && (cap? && rsvps.size <= cap - 1) end end } belongs_to :event validate { event.allowed_rsvp?(status_was) } end class Event < ApplicationRecord has_many :rsvps end

Slide 26

Slide 26 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate { event.allowed_rsvp?(status_was) } end class Event < ApplicationRecord has_many :rsvps def allowed_rsvp?(status_was) (!invite_only? || status_was == 'invited') && (cap? && rsvps.size <= cap - 1) end end

Slide 27

Slide 27 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord belongs_to :event validate { event.allowed_rsvp?(status_was) } end class Event < ApplicationRecord has_many :rsvps def allowed_rsvp?(status_was) (!invite_only? || status_was == 'invited') && (cap? && rsvps.size <= cap - 1) end end

Slide 28

Slide 28 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord using Module.new { refine Event do def allowed_rsvp?(status_was) (!invite_only? || status_was == 'invited') && (cap? && rsvps.size <= cap - 1) end end } belongs_to :event validate { event.allowed_rsvp?(status_was) } end class Event < ApplicationRecord has_many :rsvps end

Slide 29

Slide 29 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << [ event.name, event.type, event.starts_at, event.ends_at, event.sponsored, event.rvsps.size ] end end end end

Slide 30

Slide 30 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << [ event.name, event.type, event.starts_at, event.ends_at, event.sponsored, event.rvsps.size ] end end end end

Slide 31

Slide 31 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << [ event.name, event.type, event.starts_at, event.ends_at, event.sponsored, event.rvsps.size ] end end end end

Slide 32

Slide 32 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << [ event.name, event.type, event.starts_at, event.ends_at, event.sponsored, event.rvsps.size ] end end end end

Slide 33

Slide 33 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << [ event.name, event.type, event.starts_at, event.ends_at, event.sponsored, event.rvsps.size ] end end end end

Slide 34

Slide 34 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << [ event.name, event.type, event.starts_at, event.ends_at, event.sponsored, event.rvsps.size ] end end end end

Slide 35

Slide 35 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << [ event.name, event.type, event.starts_at, event.ends_at, event.sponsored, event.rvsps.size ] end end end end

Slide 36

Slide 36 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << to_row(event) end end end private def to_row(event) [ event.name, event.type, event.starts_at, event.ends_at, event.sponsored, event.rvsps.size ] end end

Slide 37

Slide 37 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << event.to_row end end end end class Event < ApplicationRecord def to_row [ name, type, starts_at, ends_at, sponsored, rvsps.size ] end end

Slide 38

Slide 38 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob using Module.new { refine Event do def to_row [ name, type, starts_at, ends_at, sponsored, rvsps.size ] end end } def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << event.to_row end end end end

Slide 39

Slide 39 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << event.to_row end end end end class Event < ApplicationRecord def to_row [ name, type, starts_at, ends_at, sponsored, rvsps.size ] end end

Slide 40

Slide 40 text

twitter.com/kddeisz Refinements in Ruby class EventExportJob < ApplicationJob using Module.new { refine Event do def to_row [ name, type, starts_at, ends_at, sponsored, rvsps.size ] end end } def perform(organization) Export.generate(organization) do |export| organization.events.each do |event| export << event.to_row end end end end

Slide 41

Slide 41 text

Common interfaces

Slide 42

Slide 42 text

twitter.com/kddeisz Refinements in Ruby class User < ApplicationRecord def can_admin_organization? superuser? end end

Slide 43

Slide 43 text

twitter.com/kddeisz Refinements in Ruby class User < ApplicationRecord def can_admin_event?(event) event.host == self end def can_admin_organization? superuser? end end

Slide 44

Slide 44 text

twitter.com/kddeisz Refinements in Ruby class User < ApplicationRecord def can_admin_event?(event) event.host == self end def can_admin_organization? superuser? end def can_admin_rsvp?(rsvp) rsvp.user == self || can_admin_event?(rsvp.event) end end

Slide 45

Slide 45 text

twitter.com/kddeisz Refinements in Ruby class User < ApplicationRecord def can_admin?(record) case record when Event record.host == self when Organization superuser? when Rsvp record.user == self || can_admin?(record.event) else raise ArgumentError end end end

Slide 46

Slide 46 text

twitter.com/kddeisz Refinements in Ruby class User < ApplicationRecord def can_admin?(record) record.admin?(self) end end class Event < ApplicationRecord def admin?(user) host == user end end class Organization < ApplicationRecord def admin?(user) user.superuser? end end class Rsvp < ApplicationRecord def admin?(other) user == other || event.admin?(other) end end

Slide 47

Slide 47 text

twitter.com/kddeisz Refinements in Ruby class User < ApplicationRecord using Module.new { refine Event do def admin?(user) host == user end end refine Organization do def admin?(user) user.superuser? end end refine Rsvp do def admin?(other) user == other || event.admin?(other) end end } def can_admin?(record) record.admin?(self) ennd

Slide 48

Slide 48 text

twitter.com/kddeisz Refinements in Ruby class AdminRules < Struct.new(:user) using Module.new { refine Event do def admin?(user) host == user end end refine Organization do def admin?(user) user.superuser? end end refine Rsvp do def admin?(other) user == other || event.admin?(other) end end } def can_admin?(record) record.admin?(user) ennd

Slide 49

Slide 49 text

twitter.com/kddeisz Refinements in Ruby class AdminRules < Struct.new(:user) using Module.new { refine Event do def admin?(user) host == user end end refine Organization do def admin?(user) user.superuser? end end refine Rsvp do def admin?(other) user == other || event.admin?(other) end end } def can_admin?(record) record.admin?(user) ennd class User < ApplicationRecord delegate :can_admin?, to: :admin_rules def admin_rules AdminRules.new(self) end end

Slide 50

Slide 50 text

twitter.com/kddeisz Refinements in Ruby class AdminRules < Struct.new(:user) using Module.new { refine Event do def admin?(user) host == user end end refine Organization do def admin?(user) user.superuser? end end refine Rsvp do def admin?(other) user == other || event.admin?(other) end end } def can_admin?(record) record.admin?(user) ennd class User < ApplicationRecord delegate :can_admin?, to: :admin_rules def admin_rules AdminRules.new(self) end end

Slide 51

Slide 51 text

Local monkey-patches

Slide 52

Slide 52 text

twitter.com/kddeisz Refinements in Ruby class Hash def except(*keys) slice(*self.keys - keys) end end class UsersController < ApplicationController def update if @user.update(params[:user].except(:admin)) render @user, status: :success else render @user.errors, status: :unprocessable_entity end end end

Slide 53

Slide 53 text

twitter.com/kddeisz Refinements in Ruby class UsersController < ApplicationController using Module.new { refine Hash do def except(*keys) slice(*self.keys - keys) end end } def update if @user.update(params[:user].except(:admin)) render @user, status: :success else render @user.errors, status: :unprocessable_entity end end end

Slide 54

Slide 54 text

twitter.com/kddeisz Refinements in Ruby class Object def blank? respond_to?(:empty?) ? !!empty? : !self end end class NilClass def blank? true end end class FalseClass def blank? true end end class TrueClass def blank? false end end class Array alias_method :blank?, :empty? end class Hash alias_method :blank?, :empty? end class String def blank? empty? || BLANK_RE.match?(self) end end

Slide 55

Slide 55 text

twitter.com/kddeisz Refinements in Ruby module Blank refine Object do def blank? respond_to?(:empty?) ? !!empty? : !self end end refine NilClass do def blank? true end end refine FalseClass do def blank? true end end refine TrueClass do def blank? false end ennd module Blank refine Array do alias_method :blank?, :empty? end refine Hash do alias_method :blank?, :empty? end refine String do def blank? empty? || BLANK_RE.match?(self) end end end

Slide 56

Slide 56 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord before_validation do self.message = message.blank? ? nil : message end end

Slide 57

Slide 57 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord using Blank before_validation do self.message = message.blank? ? nil : message end end

Slide 58

Slide 58 text

twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord using ActiveSupport::StringExtensions before_validation do self.message = message.blank? ? nil : message end end

Slide 59

Slide 59 text

Conclusions

Slide 60

Slide 60 text

Code that changes together should live together

Slide 61

Slide 61 text

Refinements can move code out of class definitions and into where it is used

Slide 62

Slide 62 text

Use refinements to organize your code so developers don’t have to open so many files to make changes

Slide 63

Slide 63 text

Refinements in Ruby twitter.com/kddeisz