Skip to content

Instantly share code, notes, and snippets.

@mnishiguchi
Last active March 20, 2019 01:18
Show Gist options
  • Save mnishiguchi/1f424044b1d52bf78af47d24f02e3017 to your computer and use it in GitHub Desktop.
Save mnishiguchi/1f424044b1d52bf78af47d24f02e3017 to your computer and use it in GitHub Desktop.
Rails Active Record composed_of

Rails Active Record composed_of

PORO model pattern A

# migration
class CreateCallEvents < ActiveRecord::Migration[5.2]
  def change
    create_table :call_events do |t|
      t.jsonb :raw_data

      t.string :call_sid, null: false

      t.string :caller_phone, null: false
      t.string :caller_city
      t.string :caller_state
      t.string :caller_zipcode

      t.string :called_phone, null: false
      t.string :called_city
      t.string :called_state
      t.string :called_zipcode

      t.timestamps
    end
  end
end
# model
# A call event from Twilio Webhook
class CallEvent < ApplicationRecord
  validates :raw_data, :call_sid, :caller_phone, :called_phone, presence: true
  validates :call_sid, uniqueness: { case_sensitive: false }

  [:caller, :called].each do |caller_or_called|
    define_method caller_or_called do
      # getter
      CallEvent::Phone.new(
        phone: send("#{caller_or_called}_phone"),
        city: send("#{caller_or_called}_city"),
        state: send("#{caller_or_called}_state"),
        zipcode: send("#{caller_or_called}_zipcode")
      )
    end

    # setter
    define_method "#{caller_or_called}=" do |call_event_phone|
      raise ArgumentError, "must respond to phone" unless call_event_phone.respond_to?(:phone)

      self["#{caller_or_called}_phone"] = call_event_phone.phone
      self["#{caller_or_called}_city"] = call_event_phone.city
      self["#{caller_or_called}_state"] = call_event_phone.state
      self["#{caller_or_called}_zipcode"] = call_event_phone.zipcode
    end
  end

  # sub-model for caller and called
  class Phone
    attr_reader :phone, :city, :state, :zipcode

    def initialize(phone:, city: nil, state: nil, zipcode: nil)
      @phone = phone
      @city = city
      @state = state
      @zipcode = zipcode
    end

    def to_s
      phone
    end
  end
end
    private def create_call_event
      CallEvent.create!(
        raw_data: raw_data,
        call_sid: raw_data["CallSid"],
        caller: caller_info,
        called: called_info
      )
    end

    private def caller_info
      CallEvent::Phone.new(
        phone: raw_data["From"] || raw_data["Called"],
        city: raw_data["FromCity"] || raw_data["CalledCity"],
        state: raw_data["FromState"] || raw_data["CalledState"],
        zipcode: raw_data["FromZip"] || raw_data["CalledZip"]
      )
    end

    private def called_info
      CallEvent::Phone.new(
        phone: raw_data["To"] || raw_data["Called"],
        city: raw_data["ToCity"] || raw_data["CalledCity"],
        state: raw_data["ToState"] || raw_data["CalledState"],
        zipcode: raw_data["ToZip"] || raw_data["CalledZip"]
      )
    end

    private def raw_data
      @raw_data ||= params.permit(*PERMITTED_PARAM_KEYS).to_h.with_indifferent_access
    end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment