Skip to content

Instantly share code, notes, and snippets.

@dhh
Last active December 19, 2022 22:22
Show Gist options
  • Save dhh/9348053 to your computer and use it in GitHub Desktop.
Save dhh/9348053 to your computer and use it in GitHub Desktop.
# MODEL
class Case < ActiveRecord::Base
include Eventable
has_many :tasks
concerning :Assignment do
def assign_to(new_owner:, details:)
transaction do
update_assigment_attributes new_owner, details
transfer_open_tasks_to new_owner
create_initial_contact_task details
record_event :assigned, details[:comments]
end
end
private
def update_assigment_attributes(new_owner, details)
update! details.slice(:distribute_at, :distribute_rule_name).merge(owner: new_owner)
end
def transfer_open_tasks_to(new_owner)
tasks.open.each { |task| task.update! owner: new_owner }
end
def create_initial_contact_task(details)
if details[:require_initial] && owner.tasks.initials?
tasks.create! kind: :initial, details[:comments]
end
end
end
end
class Task < ActiveRecord::Base
belongs_to :user
belongs_to :case
scope :open, -> { where.not status: :closed }
end
class User
has_many :tasks do
def initials?
where(kind: :initial).exist?
end
end
end
class Event < ActiveRecord::Base
store :details, accessors: [ :activity, :creator, :comments ]
cattr_accessor :creator, instance_accessor: false
before_save :set_creator
private
def set_creator
self.creator ||= self.class.creator
end
end
module Eventable
extend ActiveSupport::Concern
included do
has_many :events
end
private
def record_event(activity, attributes = {})
events.create! attributes.merge(activity: activity, eventable: self)
end
end
# CONTROLLER
class Cases::AssignmentController < ApplicationController
before_action :set_case
def create
@case.assign_to new_owner: find_new_owner, details: assignment_params
end
private
def set_case
@case = @current_account.cases.find
end
def assignment_params
params.require(:case).permit(:comments, :distribute_at, :distribute_rule_name, :require_initial)
end
def find_new_owner
@current_account.users.find(params[:case][:owner_id])
end
end
class ApplicationController < ActionController::Base
include CurrentUser, CurrentAccount
end
module CurrentUser
extend ActiveSupport::Concern
included do
before_action :set_current_user
end
private
def set_current_user
@current_user = authorize
Event.creator = @current_user
end
end
# VIEW
class EventSummarizer
def initialize(event)
@event = event
end
def summary
summarizer.new(@event).send(summary_method)
end
private
def summarizer
Object.const_get("#{@event.eventable.class}EventSummarizer")
end
def summary_method
"#{@event.eventable.class.to_s.underscore}_#{@event.activity}_summary"
end
end
class CaseEventSummarizer < EventSummarizer
def case_assigned_summary
"Case assigned to #{@event.eventable.owner.full_name}".tap do |summary|
summary << "; #{@event.comments}" if @event.comments
end
end
end
@BiggerNoise
Copy link

I appreciate the second look, but I think we're going to have to agree to disagree on the best way to approach these sorts of problems. I think this approach significantly diminishes the clarity of intent that comes with something more command like.

However, I'll keep looking at this because you're obviously a smart guy and, after all, this is your playground. I'm open to persuasion, but I imagine you have better things to do than persuade me, so I will keep trying to understand where you're coming from.

Thanks again for the alternate approach and a huge thank you for Rails.

@haberbyte
Copy link

Quick question about the Event.creator = @current_user ...

How to deal with stuff that one might do in "rails console" or in a migration, where no @current_user is set?

Skip event creation if no creator is set?

def record_event(activity, attributes = {})
  return if Event.creator.nil?
  events.create! attributes.merge(activity: activity, eventable: self)
end

@haberbyte
Copy link

Also, what about this?
Doesn't it make more sense to save the @current_user in the User class and use that for the event creator?

class User < ActiveRecord::Base
  class << self
     def current_user
       Thread.current[:current_user]
     end

    def current_user=(user)
      Thread.current[:current_user] = user
    end
  end
end

module CurrentUser
  extend ActiveSupport::Concern

  included do
    before_action :set_current_user
  end

  private
    def set_current_user
      @current_user = authorize
      User.current_user = @current_user
    end
end

class Event < ActiveRecord::Base
  store :details, accessors: [ :activity, :creator, :comments ]

  validates :creator, presence: true
end

module Eventable
  extend ActiveSupport::Concern

  included do
    has_many :events
  end

  private
    def record_event(activity, attributes = {})
      events.create! attributes.merge(
        activity: activity,
        eventable: self,
        creator: User.current_user
      )
    end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment