Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Refinements in Ruby

Refinements in Ruby

In Ruby 2.0, refinements were introduced as a language-level feature. Since then they haven't seen wide-spread adoption in the community; it's time to change that! We'll walk through what refinements are, how they work, and what problems they can solve. Come see better ways to organize your spaghetti code without sacrificing readability.

Kevin Newton

January 14, 2020
Tweet

More Decks by Kevin Newton

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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"
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. twitter.com/kddeisz Refinements in Ruby class Rsvp < ApplicationRecord using Blank

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

    before_validation do self.message = message.blank? ? nil : message end end