SlideShare a Scribd company logo
1 of 94
Download to read offline
Replacing ActiveRecord callbacks
with Pub/Sub
edenspiekermann_
March 2nd, 2017
Niall Burkley (Edenspiekermann_)
@niallburkley | github.com/nburkley | niallburkley.com
Callbacks
ACTIVE RECORD
What are callbacks?
Business logic that you want
- before or after something happens
- tied to the lifecycle of the model
What are callbacks?
Business logic that you want
- before or after something happens
- tied to the lifecycle of the model
class Post < ActiveRecord::Base
after_commit :notify_editors, on: :create
private
def notify_editors
EditorMailer.send_notification(self).deliver_later
end
end
What are callbacks?
4
Creating a record Updating a record Deleting a record
BEFORE VALIDATION
before_validation before_validation
before_validation_on_create before_validation_on_update
AFTER VALIDATION
after_validation after_validation
before_save before_save
before_create before_update before_destroy
AFTER CRUD ACTION
after_create after_update after_destroy
after_save after_save
after_commit, on: :create after_commit, on: :update after_commit, on: :destroy
4
Creating a record Updating a record Deleting a record
BEFORE VALIDATION
before_validation before_validation
before_validation_on_create before_validation_on_update
AFTER VALIDATION
after_validation after_validation
before_save before_save
before_create before_update before_destroy
AFTER CRUD ACTION
after_create after_update after_destroy
after_save after_save
after_commit, on: :create after_commit, on: :update after_commit, on: :destroy
Unrelated Business Logic in your model
- Violation of Single Responsibility Principle
#1
6
class Message < ActiveRecord::Base
after_create :add_author_as_watcher
after_create :send_notification
private
def add_author_as_watcher
Watcher.create(watchable: self.root, user: author)
end
def send_notification
if Setting.notified_events.include?('message_posted')
Mailer.message_posted(self).deliver
end
end
end
7
class Message < ActiveRecord::Base
after_create :add_author_as_watcher
after_create :send_notification
private
def add_author_as_watcher
Watcher.create(watchable: self.root, user: author)
end
def send_notification
if Setting.notified_events.include?('message_posted')
Mailer.message_posted(self).deliver
end
end
end
8
class Message < ActiveRecord::Base
after_create :add_author_as_watcher
after_create :send_notification
private
def add_author_as_watcher
Watcher.create(watchable: self.root, user: author)
end
def send_notification
if Setting.notified_events.include?('message_posted')
Mailer.message_posted(self).deliver
end
end
end
Tight Coupling & brittle tests
#2
10
class Post < ActiveRecord::Base
after_commit :generate_feed_item, on: :create
private
def generate_feed_item
FeedItemGenerator.create(self)
end
end
10
class Post < ActiveRecord::Base
after_commit :generate_feed_item, on: :create
private
def generate_feed_item
FeedItemGenerator.create(self)
end
end
describe Post do
subject { Post.new(title: 'Hello RUG::B') }
describe 'create' do
it 'creates a new feed item' do
expect { subject.save }.to change { FeedItem.count }.by(1)
expect(FeedItem.last.title).to eq('Hello RUG::B')
end
end
end
11
class FeedItemGenerator
def self.create(subject)
FeedItem.create(
subject: subject,
owner: subject.user
)
end
end
12
class FeedItemGenerator
def self.create(subject, published)
FeedItem.create(
subject: subject,
owner: subject.user,
published: published
)
end
end
13
class Post < ActiveRecord::Base
after_commit :generate_feed_item, on: :create
private
def generate_feed_item
FeedItemGenerator.create(self)
end
end
13
class Post < ActiveRecord::Base
after_commit :generate_feed_item, on: :create
private
def generate_feed_item
FeedItemGenerator.create(self)
end
end
describe Post do
subject { Post.new(title: 'Hello RUG::B') }
describe 'create' do
it 'creates a new feed item' do
expect { subject.save }.to change { FeedItem.count }.by(1)
expect(FeedItem.last.title).to eq('Hello RUG::B')
end
end
end 13
class Post < ActiveRecord::Base
after_commit :generate_feed_item, on: :create
private
def generate_feed_item
FeedItemGenerator.create(self)
end
end
describe Post do
subject { Post.new(title: 'Hello RUG::B') }
describe 'create' do
it 'creates a new feed item' do
expect { subject.save }.to change { FeedItem.count }.by(1)
expect(FeedItem.last.title).to eq('Hello RUG::B')
end
end
end 13
class Post < ActiveRecord::Base
after_commit :generate_feed_item, on: :create
private
def generate_feed_item
FeedItemGenerator.create(self)
end
end
Your Models are going to grow
#3
15
class Post < ActiveRecord::Base
after_commit :notify_users, on: :create
private
def notify_users
PostMailer.send_notifications(self).deliver_later
end
end
16
class Post < ActiveRecord::Base
after_commit :notify_users, on: :create
after_commit :generate_feed_item, on: :create
after_commit :notify_editors, on: :create
private
def notify_users
PostMailer.send_notifications(self).deliver_later
end
def generate_feed_item
FeedItemGenerator.create(self)
end
def notify_editors
EditorMailer.send_notification(self).deliver_later
end
end
17
class Post < ActiveRecord::Base
after_commit :notify_users, on: :create
after_commit :generate_feed_item, on: :create
after_commit :notify_editors, on: :create
after_commit :add_user_points, on: :create
private
def notify_users
PostMailer.send_notifications(self).deliver_later
end
def generate_feed_item
FeedItemGenerator.create(self)
end
def notify_editors
EditorMailer.send_notification(self).deliver_later
end
def add_user_points
UserPointsService.recalculate_points(self.user)
end
end
Callbacks are Synchronous
#4
19
class Image < ActiveRecord::Base
after_commit :generate_image_renditions, on: :create
private
def generate_image_renditions
ImageService.create_renditions(self)
end
end
20
Browser Controller Image ImageService
POST /images
create(params)
create_renditions(image)
image renditions
image
success
after_create callback
20
Browser Controller Image ImageService
POST /images
create(params)
create_renditions(image)
image renditions
image
success
after_create callback
20
Browser Controller Image ImageService
POST /images
create(params)
create_renditions(image)
image renditions
image
success
after_create callback
20
Browser Controller Image ImageService
POST /images
create(params)
create_renditions(image)
image renditions
image
success
after_create callback
20
Browser Controller Image ImageService
POST /images
create(params)
create_renditions(image)
image renditions
image
success
after_create callback
20
Browser Controller Image ImageService
POST /images
create(params)
create_renditions(image)
image renditions
image
success
after_create callback
20
Browser Controller Image ImageService
POST /images
create(params)
create_renditions(image)
image renditions
image
success
after_create callback
21
Browser Controller Image Feed Generator
POST /images
create(params)
create_renditions(image)
image
success
ImageService
background process
image renditions
21
Browser Controller Image Feed Generator
POST /images
create(params)
create_renditions(image)
image
success
ImageService
background process
image renditions
21
Browser Controller Image Feed Generator
POST /images
create(params)
create_renditions(image)
image
success
ImageService
background process
image renditions
21
Browser Controller Image Feed Generator
POST /images
create(params)
create_renditions(image)
image
success
ImageService
background process
image renditions
21
Browser Controller Image Feed Generator
POST /images
create(params)
create_renditions(image)
image
success
ImageService
background process
image renditions
21
Browser Controller Image Feed Generator
POST /images
create(params)
create_renditions(image)
image
success
ImageService
background process
image renditions
21
Browser Controller Image Feed Generator
POST /images
create(params)
create_renditions(image)
image
success
ImageService
background process
image renditions
Building a
Social Platform
→ User feeds
→ Project feeds
→ User Notifications
→ Project Notifications
→ User Karma
23
class Project < ActiveRecord::Base
after_commit :add_user_points, on: :create
private
def add_user_points
UserPointsService.recalculate_points(self.user)
end
end
23
class Project < ActiveRecord::Base
after_commit :add_user_points, on: :create
private
def add_user_points
UserPointsService.recalculate_points(self.user)
end
end
class Comment < ActiveRecord::Base
after_commit :add_user_points, on: :create
private
def add_user_points
UserPointsService.recalculate_points(self.user)
end
end
24
module UpdatesUserPoints
extend ActiveSupport::Concern
included do
after_commit :add_user_points, on: :create
def add_user_points
UserPointsService.recalculate_points(self.user)
end
end
end
class Project < ActiveRecord::Base
include UpdatesUserPoints
end
class Comment < ActiveRecord::Base
include UpdatesUserPoints
end
Wisper
Wisper
A library providing Ruby objects with Publish-Subscribe capabilities
•Decouples core business logic from external concerns
•An alternative to ActiveRecord callbacks and Observers in Rails apps
•Connect objects based on context without permanence
•React to events synchronously or asynchronously
27
Project
ProjectSubscriber
UserSubscriber
create
User
create
Events
project_created(self)
user_created(self)
user_created(user)
project_ created(post)
<broadcast>
<broadcast>
28
class Project < ActiveRecord::Base
include Wisper::Publisher
after_commit :publish_creation, on: :create
private
def publish_creation
broadcast(:project_created, self)
end
end
29
Project.subscribe(ProjectSubscriber.new)
30
class ProjectSubscriber
def project_created(project)
UserPointsService.recalculate_points(project)
end
end
30
class ProjectSubscriber
def project_created(project)
UserPointsService.recalculate_points(project)
end
end
class Project < ActiveRecord::Base
include Wisper::Publisher
after_commit :publish_creation, on: :create
private
def publish_creation
broadcast(:project_created, self)
end
end
30
class ProjectSubscriber
def project_created(project)
UserPointsService.recalculate_points(project)
end
end
class Project < ActiveRecord::Base
include Wisper::Publisher
after_commit :publish_creation, on: :create
private
def publish_creation
broadcast(:project_created, self)
end
end
30
class ProjectSubscriber
def project_created(project)
UserPointsService.recalculate_points(project)
end
end
class Project < ActiveRecord::Base
include Wisper::Publisher
after_commit :publish_creation, on: :create
private
def publish_creation
broadcast(:project_created, self)
end
end
31
Too much boilerplate
Wisper::ActiveRecord
Wisper::ActiveRecord
Broadcast Lifecycle Events
after_create
after_destroy
create_<model_name>_{successful, failed}
update_<model_name>_{successful, failed}
destroy_<model_name>_{successful, failed}
<model_name>_committed
after_commit
after_rollback
34
class Project < ActiveRecord::Base
include Wisper::Publisher
after_commit :publish_creation, on: :create
private
def publish_creation
broadcast(:project_created, self)
end
end
35
class Project < ActiveRecord::Base
include Wisper.model
end
35
class Project < ActiveRecord::Base
include Wisper.model
end
Project.subscribe(ProjectSubscriber.new)
35
class Project < ActiveRecord::Base
include Wisper.model
end
class ProjectSubscriber
def after_create(project)
UserPointsService.recalculate_points(project)
end
end
Project.subscribe(ProjectSubscriber.new)
36
Make it Asynchronous
Wisper::ActiveJob
38
Post.subscribe(ProjectSubscriber, async: true)
38
Post.subscribe(ProjectSubscriber, async: true)
38
class ProjectSubscriber
def self.post_created(post)
UserPointsService.recalculate_points(post)
end
end
Post.subscribe(ProjectSubscriber, async: true)
39
What about tests?
40
class Message < ActiveRecord::Base
after_create :add_author_as_watcher
after_create :reset_counters!
after_create :send_notification
end
41
class MessageTest < ActiveSupport::TestCase
def test_create
topics_count = @board.topics_count
messages_count = @board.messages_count
message = Message.new(board: @board,
subject: 'Test message',
content: 'Test message content',
author: @user)
assert message.save
@board.reload
# topics count incremented
assert_equal topics_count + 1, @board[:topics_count]
# messages count incremented
assert_equal messages_count + 1, @board[:messages_count]
assert_equal message, @board.last_message
# author should be watching the message
assert message.watched_by?(@user)
end
end
42
class MessageTest < ActiveSupport::TestCase
def test_create
topics_count = @board.topics_count
messages_count = @board.messages_count
message = Message.new(board: @board,
subject: 'Test message',
content: 'Test message content',
author: @user)
assert message.save
@board.reload
# topics count incremented
assert_equal topics_count + 1, @board[:topics_count]
# messages count incremented
assert_equal messages_count + 1, @board[:messages_count]
assert_equal message, @board.last_message
# author should be watching the message
assert message.watched_by?(@user)
end
end
Wisper::Rspec
44
describe Message do
subject { Message.new(text: 'Hello RUG::B') }
describe 'create' do
it 'broadcasts message creation' do
expect { subject.save }.to broadcast(:after_create, subject)
end
end
end
45
describe MessageSubscriber do
let(:message) { Message.create(text: 'Hello RUG::B') }
describe 'after_create' do
it 'adds message author as watcher' do
MessageSubscriber.after_create(message)
expect(Watcher.last.user).to eq(message.author)
end
it 'adds updates the board counter' do
expect { MessageSubscriber.after_create(message) }
.to change { message.board.count }.by(1)
end
it 'sends a notification' do
MessageSubscriber.after_create(message)
expect(UserMailer).to receive(:send_notification).with(message.board.owner)
end
end
end
46
What have we achieved?
46
What have we achieved?
#1 - We’ve removed unrelated business logic from our classes
46
What have we achieved?
#1 - We’ve removed unrelated business logic from our classes
#2 - Decoupled our callbacks, making them easier to test
46
What have we achieved?
#1 - We’ve removed unrelated business logic from our classes
#2 - Decoupled our callbacks, making them easier to test
#3 -DRY’d up and slimmed down our model code
46
What have we achieved?
#1 - We’ve removed unrelated business logic from our classes
#2 - Decoupled our callbacks, making them easier to test
#3 -DRY’d up and slimmed down our model code
#4 - Moved our callback logic into background jobs
47
Alternatives?
48
Alternatives - Observers
48
Alternatives - Observers
class CommentObserver < ActiveRecord::Observer
def after_save(comment)
EditorMailer.comment_notification(comment).deliver
end
end
49
Alternatives - Decorators
49
Alternatives - Decorators
class CommentDecorator < ApplicationDecorator
decorates Comment
def create
save && send_notification
end
private
def send_notification
EditorMailer.comment_notification(comment).deliver
end
end
50
Alternatives - Decorators
class CommentController < ApplicationController
def create
@comment = CommentDecorator.new(Comment.new(comment_params))
if @comment.create
# handle the success
else
# handle the success
end
end
end
51
Alternatives - Trailblazer
51
Alternatives - Trailblazer
class Comment::Create < Trailblazer::Operation
callback :after_save, EditorNotificationCallback
51
Alternatives - Trailblazer
class EditorNotificationCallback
def initialize(comment)
@comment = comment
end
def call(options)
EditorMailer.comment_notification(@comment).deliver
end
end
class Comment::Create < Trailblazer::Operation
callback :after_save, EditorNotificationCallback
52
Wisper
52
Wisper
→ Lightweight and clean integration
52
Wisper
→ Lightweight and clean integration
→ Well tested and maintained
52
Wisper
→ Lightweight and clean integration
→ Well tested and maintained
→ Plenty of integration options
52
Wisper
→ Lightweight and clean integration
→ Well tested and maintained
→ Plenty of integration options
→ Not just for Rails or ActiveRecord
52
Wisper
→ Lightweight and clean integration
→ Well tested and maintained
→ Plenty of integration options
→ Not just for Rails or ActiveRecord
→ Great for small to medium scale, and potentially more
52
Wisper
→ Lightweight and clean integration
→ Well tested and maintained
→ Plenty of integration options
→ Not just for Rails or ActiveRecord
→ Great for small to medium scale, and potentially more
→ It’s the right tool for the job for us
(👋 & Thanks) unless questions?
Niall Burkley (Edenspiekermann_)
@niallburkley | github.com/nburkley | niallburkley.com

More Related Content

Viewers also liked

Quick Dissolve Retailer Presentation
Quick Dissolve Retailer PresentationQuick Dissolve Retailer Presentation
Quick Dissolve Retailer Presentationdorment
 
Action taken by Hon'ble Supreme Court of India
Action taken by Hon'ble Supreme Court of IndiaAction taken by Hon'ble Supreme Court of India
Action taken by Hon'ble Supreme Court of IndiaOm Prakash Poddar
 
¿Cómo distribuye las ganancias Uber?
¿Cómo distribuye las ganancias Uber?¿Cómo distribuye las ganancias Uber?
¿Cómo distribuye las ganancias Uber?simon gomez yaque
 
Dynamo presentazione #coopupbo - 5-10-2016
Dynamo   presentazione #coopupbo - 5-10-2016Dynamo   presentazione #coopupbo - 5-10-2016
Dynamo presentazione #coopupbo - 5-10-2016Kilowatt
 
Evaluación del-desempeño-docente-20-02-17
Evaluación del-desempeño-docente-20-02-17Evaluación del-desempeño-docente-20-02-17
Evaluación del-desempeño-docente-20-02-17luis1603
 
Cuadernillo de primero de secundaria español
Cuadernillo de primero de secundaria españolCuadernillo de primero de secundaria español
Cuadernillo de primero de secundaria españolSheryl Rojas
 
Antecedentes y ubicación de la Sociología como ciencia
Antecedentes y ubicación de la Sociología como cienciaAntecedentes y ubicación de la Sociología como ciencia
Antecedentes y ubicación de la Sociología como cienciaFrancia Zárate C.
 
Test Driven Problem Solving - AOC Argentina 2017
Test Driven Problem Solving - AOC Argentina 2017Test Driven Problem Solving - AOC Argentina 2017
Test Driven Problem Solving - AOC Argentina 2017LeanSight Consulting
 
La necesidad de corregir el presupuesto 2017, y prioridades para un ajuste fi...
La necesidad de corregir el presupuesto 2017, y prioridades para un ajuste fi...La necesidad de corregir el presupuesto 2017, y prioridades para un ajuste fi...
La necesidad de corregir el presupuesto 2017, y prioridades para un ajuste fi...FUSADES
 
04. Debora Miceli: Remuneração - Conceitualização e desenho de cargos
04. Debora Miceli: Remuneração - Conceitualização e desenho de cargos04. Debora Miceli: Remuneração - Conceitualização e desenho de cargos
04. Debora Miceli: Remuneração - Conceitualização e desenho de cargosDebora Miceli
 
Hack&Beers Cadiz Análisis de Malware Cuckoo Sandbox
Hack&Beers Cadiz Análisis de Malware Cuckoo SandboxHack&Beers Cadiz Análisis de Malware Cuckoo Sandbox
Hack&Beers Cadiz Análisis de Malware Cuckoo SandboxMario Alberto Parra Alonso
 

Viewers also liked (17)

Quick Dissolve Retailer Presentation
Quick Dissolve Retailer PresentationQuick Dissolve Retailer Presentation
Quick Dissolve Retailer Presentation
 
Action taken by Hon'ble Supreme Court of India
Action taken by Hon'ble Supreme Court of IndiaAction taken by Hon'ble Supreme Court of India
Action taken by Hon'ble Supreme Court of India
 
¿Cómo distribuye las ganancias Uber?
¿Cómo distribuye las ganancias Uber?¿Cómo distribuye las ganancias Uber?
¿Cómo distribuye las ganancias Uber?
 
Dynamo presentazione #coopupbo - 5-10-2016
Dynamo   presentazione #coopupbo - 5-10-2016Dynamo   presentazione #coopupbo - 5-10-2016
Dynamo presentazione #coopupbo - 5-10-2016
 
Evaluación del-desempeño-docente-20-02-17
Evaluación del-desempeño-docente-20-02-17Evaluación del-desempeño-docente-20-02-17
Evaluación del-desempeño-docente-20-02-17
 
Nuevo planetario
Nuevo planetarioNuevo planetario
Nuevo planetario
 
WARRASE
WARRASEWARRASE
WARRASE
 
Q6 evaluation
Q6 evaluationQ6 evaluation
Q6 evaluation
 
Cuadernillo de primero de secundaria español
Cuadernillo de primero de secundaria españolCuadernillo de primero de secundaria español
Cuadernillo de primero de secundaria español
 
Antecedentes y ubicación de la Sociología como ciencia
Antecedentes y ubicación de la Sociología como cienciaAntecedentes y ubicación de la Sociología como ciencia
Antecedentes y ubicación de la Sociología como ciencia
 
Si al número 7893 le agrego 27 números más
Si al número 7893 le agrego 27 números másSi al número 7893 le agrego 27 números más
Si al número 7893 le agrego 27 números más
 
Test Driven Problem Solving - AOC Argentina 2017
Test Driven Problem Solving - AOC Argentina 2017Test Driven Problem Solving - AOC Argentina 2017
Test Driven Problem Solving - AOC Argentina 2017
 
La necesidad de corregir el presupuesto 2017, y prioridades para un ajuste fi...
La necesidad de corregir el presupuesto 2017, y prioridades para un ajuste fi...La necesidad de corregir el presupuesto 2017, y prioridades para un ajuste fi...
La necesidad de corregir el presupuesto 2017, y prioridades para un ajuste fi...
 
Battlefield 1
Battlefield 1Battlefield 1
Battlefield 1
 
04. Debora Miceli: Remuneração - Conceitualização e desenho de cargos
04. Debora Miceli: Remuneração - Conceitualização e desenho de cargos04. Debora Miceli: Remuneração - Conceitualização e desenho de cargos
04. Debora Miceli: Remuneração - Conceitualização e desenho de cargos
 
AMIT.R DOC
AMIT.R DOCAMIT.R DOC
AMIT.R DOC
 
Hack&Beers Cadiz Análisis de Malware Cuckoo Sandbox
Hack&Beers Cadiz Análisis de Malware Cuckoo SandboxHack&Beers Cadiz Análisis de Malware Cuckoo Sandbox
Hack&Beers Cadiz Análisis de Malware Cuckoo Sandbox
 

Similar to Replacing ActiveRecord callbacks with Pub/Sub

Crud operations using aws dynamo db with flask ap is and boto3
Crud operations using aws dynamo db with flask ap is and boto3Crud operations using aws dynamo db with flask ap is and boto3
Crud operations using aws dynamo db with flask ap is and boto3Katy Slemon
 
Test-driven Development with Drupal and Codeception (DrupalCamp Brighton)
Test-driven Development with Drupal and Codeception (DrupalCamp Brighton)Test-driven Development with Drupal and Codeception (DrupalCamp Brighton)
Test-driven Development with Drupal and Codeception (DrupalCamp Brighton)Cogapp
 
Introduction to backbone presentation
Introduction to backbone presentationIntroduction to backbone presentation
Introduction to backbone presentationBrian Hogg
 
OpenWhisk Under the Hood -- London Oct 16 2016
OpenWhisk Under the Hood -- London Oct 16 2016OpenWhisk Under the Hood -- London Oct 16 2016
OpenWhisk Under the Hood -- London Oct 16 2016Stephen Fink
 
Python from zero to hero (Twitter Explorer)
Python from zero to hero (Twitter Explorer)Python from zero to hero (Twitter Explorer)
Python from zero to hero (Twitter Explorer)Yuriy Senko
 
TurboGears2 Pluggable Applications
TurboGears2 Pluggable ApplicationsTurboGears2 Pluggable Applications
TurboGears2 Pluggable ApplicationsAlessandro Molina
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkBo-Yi Wu
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Elyse Kolker Gordon
 
ReactJS for Programmers
ReactJS for ProgrammersReactJS for Programmers
ReactJS for ProgrammersDavid Rodenas
 
Tips and tricks for building api heavy ruby on rails applications
Tips and tricks for building api heavy ruby on rails applicationsTips and tricks for building api heavy ruby on rails applications
Tips and tricks for building api heavy ruby on rails applicationsTim Cull
 
Writing automation tests with python selenium behave pageobjects
Writing automation tests with python selenium behave pageobjectsWriting automation tests with python selenium behave pageobjects
Writing automation tests with python selenium behave pageobjectsLeticia Rss
 
DevOps Hackathon: Session 3 - Test Driven Infrastructure
DevOps Hackathon: Session 3 - Test Driven InfrastructureDevOps Hackathon: Session 3 - Test Driven Infrastructure
DevOps Hackathon: Session 3 - Test Driven InfrastructureAntons Kranga
 
Laravel Design Patterns
Laravel Design PatternsLaravel Design Patterns
Laravel Design PatternsBobby Bouwmann
 
WordPress Developers Israel Meetup #1
WordPress Developers Israel Meetup #1WordPress Developers Israel Meetup #1
WordPress Developers Israel Meetup #1Yoav Farhi
 
Simple restfull app_s
Simple restfull app_sSimple restfull app_s
Simple restfull app_snetwix
 
Quick Fetch API Introduction
Quick Fetch API IntroductionQuick Fetch API Introduction
Quick Fetch API IntroductionChris Love
 

Similar to Replacing ActiveRecord callbacks with Pub/Sub (20)

Crud operations using aws dynamo db with flask ap is and boto3
Crud operations using aws dynamo db with flask ap is and boto3Crud operations using aws dynamo db with flask ap is and boto3
Crud operations using aws dynamo db with flask ap is and boto3
 
Test-driven Development with Drupal and Codeception (DrupalCamp Brighton)
Test-driven Development with Drupal and Codeception (DrupalCamp Brighton)Test-driven Development with Drupal and Codeception (DrupalCamp Brighton)
Test-driven Development with Drupal and Codeception (DrupalCamp Brighton)
 
Introduction to backbone presentation
Introduction to backbone presentationIntroduction to backbone presentation
Introduction to backbone presentation
 
OpenWhisk Under the Hood -- London Oct 16 2016
OpenWhisk Under the Hood -- London Oct 16 2016OpenWhisk Under the Hood -- London Oct 16 2016
OpenWhisk Under the Hood -- London Oct 16 2016
 
Android workshop
Android workshopAndroid workshop
Android workshop
 
Python from zero to hero (Twitter Explorer)
Python from zero to hero (Twitter Explorer)Python from zero to hero (Twitter Explorer)
Python from zero to hero (Twitter Explorer)
 
TurboGears2 Pluggable Applications
TurboGears2 Pluggable ApplicationsTurboGears2 Pluggable Applications
TurboGears2 Pluggable Applications
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC Framework
 
Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017Building Universal Web Apps with React ForwardJS 2017
Building Universal Web Apps with React ForwardJS 2017
 
os-php-wiki5-a4
os-php-wiki5-a4os-php-wiki5-a4
os-php-wiki5-a4
 
os-php-wiki5-a4
os-php-wiki5-a4os-php-wiki5-a4
os-php-wiki5-a4
 
ReactJS for Programmers
ReactJS for ProgrammersReactJS for Programmers
ReactJS for Programmers
 
Tips and tricks for building api heavy ruby on rails applications
Tips and tricks for building api heavy ruby on rails applicationsTips and tricks for building api heavy ruby on rails applications
Tips and tricks for building api heavy ruby on rails applications
 
Writing automation tests with python selenium behave pageobjects
Writing automation tests with python selenium behave pageobjectsWriting automation tests with python selenium behave pageobjects
Writing automation tests with python selenium behave pageobjects
 
DevOps Hackathon: Session 3 - Test Driven Infrastructure
DevOps Hackathon: Session 3 - Test Driven InfrastructureDevOps Hackathon: Session 3 - Test Driven Infrastructure
DevOps Hackathon: Session 3 - Test Driven Infrastructure
 
Laravel Design Patterns
Laravel Design PatternsLaravel Design Patterns
Laravel Design Patterns
 
WordPress Developers Israel Meetup #1
WordPress Developers Israel Meetup #1WordPress Developers Israel Meetup #1
WordPress Developers Israel Meetup #1
 
Simple restfull app_s
Simple restfull app_sSimple restfull app_s
Simple restfull app_s
 
Zend framework
Zend frameworkZend framework
Zend framework
 
Quick Fetch API Introduction
Quick Fetch API IntroductionQuick Fetch API Introduction
Quick Fetch API Introduction
 

Recently uploaded

08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksSoftradix Technologies
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptxLBM Solutions
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...HostedbyConfluent
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
Azure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAzure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAndikSusilo4
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhisoniya singh
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 

Recently uploaded (20)

08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other Frameworks
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptx
 
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
Transforming Data Streams with Kafka Connect: An Introduction to Single Messa...
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
Azure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & ApplicationAzure Monitor & Application Insight to monitor Infrastructure & Application
Azure Monitor & Application Insight to monitor Infrastructure & Application
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
 
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | DelhiFULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
FULL ENJOY 🔝 8264348440 🔝 Call Girls in Diplomatic Enclave | Delhi
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 

Replacing ActiveRecord callbacks with Pub/Sub

  • 1. Replacing ActiveRecord callbacks with Pub/Sub edenspiekermann_ March 2nd, 2017 Niall Burkley (Edenspiekermann_) @niallburkley | github.com/nburkley | niallburkley.com
  • 4. Business logic that you want - before or after something happens - tied to the lifecycle of the model What are callbacks?
  • 5. Business logic that you want - before or after something happens - tied to the lifecycle of the model class Post < ActiveRecord::Base after_commit :notify_editors, on: :create private def notify_editors EditorMailer.send_notification(self).deliver_later end end What are callbacks?
  • 6. 4 Creating a record Updating a record Deleting a record BEFORE VALIDATION before_validation before_validation before_validation_on_create before_validation_on_update AFTER VALIDATION after_validation after_validation before_save before_save before_create before_update before_destroy AFTER CRUD ACTION after_create after_update after_destroy after_save after_save after_commit, on: :create after_commit, on: :update after_commit, on: :destroy
  • 7. 4 Creating a record Updating a record Deleting a record BEFORE VALIDATION before_validation before_validation before_validation_on_create before_validation_on_update AFTER VALIDATION after_validation after_validation before_save before_save before_create before_update before_destroy AFTER CRUD ACTION after_create after_update after_destroy after_save after_save after_commit, on: :create after_commit, on: :update after_commit, on: :destroy
  • 8. Unrelated Business Logic in your model - Violation of Single Responsibility Principle #1
  • 9. 6 class Message < ActiveRecord::Base after_create :add_author_as_watcher after_create :send_notification private def add_author_as_watcher Watcher.create(watchable: self.root, user: author) end def send_notification if Setting.notified_events.include?('message_posted') Mailer.message_posted(self).deliver end end end
  • 10. 7 class Message < ActiveRecord::Base after_create :add_author_as_watcher after_create :send_notification private def add_author_as_watcher Watcher.create(watchable: self.root, user: author) end def send_notification if Setting.notified_events.include?('message_posted') Mailer.message_posted(self).deliver end end end
  • 11. 8 class Message < ActiveRecord::Base after_create :add_author_as_watcher after_create :send_notification private def add_author_as_watcher Watcher.create(watchable: self.root, user: author) end def send_notification if Setting.notified_events.include?('message_posted') Mailer.message_posted(self).deliver end end end
  • 12. Tight Coupling & brittle tests #2
  • 13. 10 class Post < ActiveRecord::Base after_commit :generate_feed_item, on: :create private def generate_feed_item FeedItemGenerator.create(self) end end
  • 14. 10 class Post < ActiveRecord::Base after_commit :generate_feed_item, on: :create private def generate_feed_item FeedItemGenerator.create(self) end end describe Post do subject { Post.new(title: 'Hello RUG::B') } describe 'create' do it 'creates a new feed item' do expect { subject.save }.to change { FeedItem.count }.by(1) expect(FeedItem.last.title).to eq('Hello RUG::B') end end end
  • 16. 12 class FeedItemGenerator def self.create(subject, published) FeedItem.create( subject: subject, owner: subject.user, published: published ) end end
  • 17. 13 class Post < ActiveRecord::Base after_commit :generate_feed_item, on: :create private def generate_feed_item FeedItemGenerator.create(self) end end
  • 18. 13 class Post < ActiveRecord::Base after_commit :generate_feed_item, on: :create private def generate_feed_item FeedItemGenerator.create(self) end end
  • 19. describe Post do subject { Post.new(title: 'Hello RUG::B') } describe 'create' do it 'creates a new feed item' do expect { subject.save }.to change { FeedItem.count }.by(1) expect(FeedItem.last.title).to eq('Hello RUG::B') end end end 13 class Post < ActiveRecord::Base after_commit :generate_feed_item, on: :create private def generate_feed_item FeedItemGenerator.create(self) end end
  • 20. describe Post do subject { Post.new(title: 'Hello RUG::B') } describe 'create' do it 'creates a new feed item' do expect { subject.save }.to change { FeedItem.count }.by(1) expect(FeedItem.last.title).to eq('Hello RUG::B') end end end 13 class Post < ActiveRecord::Base after_commit :generate_feed_item, on: :create private def generate_feed_item FeedItemGenerator.create(self) end end
  • 21. Your Models are going to grow #3
  • 22. 15 class Post < ActiveRecord::Base after_commit :notify_users, on: :create private def notify_users PostMailer.send_notifications(self).deliver_later end end
  • 23. 16 class Post < ActiveRecord::Base after_commit :notify_users, on: :create after_commit :generate_feed_item, on: :create after_commit :notify_editors, on: :create private def notify_users PostMailer.send_notifications(self).deliver_later end def generate_feed_item FeedItemGenerator.create(self) end def notify_editors EditorMailer.send_notification(self).deliver_later end end
  • 24. 17 class Post < ActiveRecord::Base after_commit :notify_users, on: :create after_commit :generate_feed_item, on: :create after_commit :notify_editors, on: :create after_commit :add_user_points, on: :create private def notify_users PostMailer.send_notifications(self).deliver_later end def generate_feed_item FeedItemGenerator.create(self) end def notify_editors EditorMailer.send_notification(self).deliver_later end def add_user_points UserPointsService.recalculate_points(self.user) end end
  • 26. 19 class Image < ActiveRecord::Base after_commit :generate_image_renditions, on: :create private def generate_image_renditions ImageService.create_renditions(self) end end
  • 27. 20 Browser Controller Image ImageService POST /images create(params) create_renditions(image) image renditions image success after_create callback
  • 28. 20 Browser Controller Image ImageService POST /images create(params) create_renditions(image) image renditions image success after_create callback
  • 29. 20 Browser Controller Image ImageService POST /images create(params) create_renditions(image) image renditions image success after_create callback
  • 30. 20 Browser Controller Image ImageService POST /images create(params) create_renditions(image) image renditions image success after_create callback
  • 31. 20 Browser Controller Image ImageService POST /images create(params) create_renditions(image) image renditions image success after_create callback
  • 32. 20 Browser Controller Image ImageService POST /images create(params) create_renditions(image) image renditions image success after_create callback
  • 33. 20 Browser Controller Image ImageService POST /images create(params) create_renditions(image) image renditions image success after_create callback
  • 34. 21 Browser Controller Image Feed Generator POST /images create(params) create_renditions(image) image success ImageService background process image renditions
  • 35. 21 Browser Controller Image Feed Generator POST /images create(params) create_renditions(image) image success ImageService background process image renditions
  • 36. 21 Browser Controller Image Feed Generator POST /images create(params) create_renditions(image) image success ImageService background process image renditions
  • 37. 21 Browser Controller Image Feed Generator POST /images create(params) create_renditions(image) image success ImageService background process image renditions
  • 38. 21 Browser Controller Image Feed Generator POST /images create(params) create_renditions(image) image success ImageService background process image renditions
  • 39. 21 Browser Controller Image Feed Generator POST /images create(params) create_renditions(image) image success ImageService background process image renditions
  • 40. 21 Browser Controller Image Feed Generator POST /images create(params) create_renditions(image) image success ImageService background process image renditions
  • 41. Building a Social Platform → User feeds → Project feeds → User Notifications → Project Notifications → User Karma
  • 42. 23 class Project < ActiveRecord::Base after_commit :add_user_points, on: :create private def add_user_points UserPointsService.recalculate_points(self.user) end end
  • 43. 23 class Project < ActiveRecord::Base after_commit :add_user_points, on: :create private def add_user_points UserPointsService.recalculate_points(self.user) end end class Comment < ActiveRecord::Base after_commit :add_user_points, on: :create private def add_user_points UserPointsService.recalculate_points(self.user) end end
  • 44. 24 module UpdatesUserPoints extend ActiveSupport::Concern included do after_commit :add_user_points, on: :create def add_user_points UserPointsService.recalculate_points(self.user) end end end class Project < ActiveRecord::Base include UpdatesUserPoints end class Comment < ActiveRecord::Base include UpdatesUserPoints end
  • 46. Wisper A library providing Ruby objects with Publish-Subscribe capabilities •Decouples core business logic from external concerns •An alternative to ActiveRecord callbacks and Observers in Rails apps •Connect objects based on context without permanence •React to events synchronously or asynchronously
  • 48. 28 class Project < ActiveRecord::Base include Wisper::Publisher after_commit :publish_creation, on: :create private def publish_creation broadcast(:project_created, self) end end
  • 51. 30 class ProjectSubscriber def project_created(project) UserPointsService.recalculate_points(project) end end class Project < ActiveRecord::Base include Wisper::Publisher after_commit :publish_creation, on: :create private def publish_creation broadcast(:project_created, self) end end
  • 52. 30 class ProjectSubscriber def project_created(project) UserPointsService.recalculate_points(project) end end class Project < ActiveRecord::Base include Wisper::Publisher after_commit :publish_creation, on: :create private def publish_creation broadcast(:project_created, self) end end
  • 53. 30 class ProjectSubscriber def project_created(project) UserPointsService.recalculate_points(project) end end class Project < ActiveRecord::Base include Wisper::Publisher after_commit :publish_creation, on: :create private def publish_creation broadcast(:project_created, self) end end
  • 56. Wisper::ActiveRecord Broadcast Lifecycle Events after_create after_destroy create_<model_name>_{successful, failed} update_<model_name>_{successful, failed} destroy_<model_name>_{successful, failed} <model_name>_committed after_commit after_rollback
  • 57. 34 class Project < ActiveRecord::Base include Wisper::Publisher after_commit :publish_creation, on: :create private def publish_creation broadcast(:project_created, self) end end
  • 58. 35 class Project < ActiveRecord::Base include Wisper.model end
  • 59. 35 class Project < ActiveRecord::Base include Wisper.model end Project.subscribe(ProjectSubscriber.new)
  • 60. 35 class Project < ActiveRecord::Base include Wisper.model end class ProjectSubscriber def after_create(project) UserPointsService.recalculate_points(project) end end Project.subscribe(ProjectSubscriber.new)
  • 67. 40 class Message < ActiveRecord::Base after_create :add_author_as_watcher after_create :reset_counters! after_create :send_notification end
  • 68. 41 class MessageTest < ActiveSupport::TestCase def test_create topics_count = @board.topics_count messages_count = @board.messages_count message = Message.new(board: @board, subject: 'Test message', content: 'Test message content', author: @user) assert message.save @board.reload # topics count incremented assert_equal topics_count + 1, @board[:topics_count] # messages count incremented assert_equal messages_count + 1, @board[:messages_count] assert_equal message, @board.last_message # author should be watching the message assert message.watched_by?(@user) end end
  • 69. 42 class MessageTest < ActiveSupport::TestCase def test_create topics_count = @board.topics_count messages_count = @board.messages_count message = Message.new(board: @board, subject: 'Test message', content: 'Test message content', author: @user) assert message.save @board.reload # topics count incremented assert_equal topics_count + 1, @board[:topics_count] # messages count incremented assert_equal messages_count + 1, @board[:messages_count] assert_equal message, @board.last_message # author should be watching the message assert message.watched_by?(@user) end end
  • 71. 44 describe Message do subject { Message.new(text: 'Hello RUG::B') } describe 'create' do it 'broadcasts message creation' do expect { subject.save }.to broadcast(:after_create, subject) end end end
  • 72. 45 describe MessageSubscriber do let(:message) { Message.create(text: 'Hello RUG::B') } describe 'after_create' do it 'adds message author as watcher' do MessageSubscriber.after_create(message) expect(Watcher.last.user).to eq(message.author) end it 'adds updates the board counter' do expect { MessageSubscriber.after_create(message) } .to change { message.board.count }.by(1) end it 'sends a notification' do MessageSubscriber.after_create(message) expect(UserMailer).to receive(:send_notification).with(message.board.owner) end end end
  • 73. 46 What have we achieved?
  • 74. 46 What have we achieved? #1 - We’ve removed unrelated business logic from our classes
  • 75. 46 What have we achieved? #1 - We’ve removed unrelated business logic from our classes #2 - Decoupled our callbacks, making them easier to test
  • 76. 46 What have we achieved? #1 - We’ve removed unrelated business logic from our classes #2 - Decoupled our callbacks, making them easier to test #3 -DRY’d up and slimmed down our model code
  • 77. 46 What have we achieved? #1 - We’ve removed unrelated business logic from our classes #2 - Decoupled our callbacks, making them easier to test #3 -DRY’d up and slimmed down our model code #4 - Moved our callback logic into background jobs
  • 80. 48 Alternatives - Observers class CommentObserver < ActiveRecord::Observer def after_save(comment) EditorMailer.comment_notification(comment).deliver end end
  • 82. 49 Alternatives - Decorators class CommentDecorator < ApplicationDecorator decorates Comment def create save && send_notification end private def send_notification EditorMailer.comment_notification(comment).deliver end end
  • 83. 50 Alternatives - Decorators class CommentController < ApplicationController def create @comment = CommentDecorator.new(Comment.new(comment_params)) if @comment.create # handle the success else # handle the success end end end
  • 85. 51 Alternatives - Trailblazer class Comment::Create < Trailblazer::Operation callback :after_save, EditorNotificationCallback
  • 86. 51 Alternatives - Trailblazer class EditorNotificationCallback def initialize(comment) @comment = comment end def call(options) EditorMailer.comment_notification(@comment).deliver end end class Comment::Create < Trailblazer::Operation callback :after_save, EditorNotificationCallback
  • 88. 52 Wisper → Lightweight and clean integration
  • 89. 52 Wisper → Lightweight and clean integration → Well tested and maintained
  • 90. 52 Wisper → Lightweight and clean integration → Well tested and maintained → Plenty of integration options
  • 91. 52 Wisper → Lightweight and clean integration → Well tested and maintained → Plenty of integration options → Not just for Rails or ActiveRecord
  • 92. 52 Wisper → Lightweight and clean integration → Well tested and maintained → Plenty of integration options → Not just for Rails or ActiveRecord → Great for small to medium scale, and potentially more
  • 93. 52 Wisper → Lightweight and clean integration → Well tested and maintained → Plenty of integration options → Not just for Rails or ActiveRecord → Great for small to medium scale, and potentially more → It’s the right tool for the job for us
  • 94. (👋 & Thanks) unless questions? Niall Burkley (Edenspiekermann_) @niallburkley | github.com/nburkley | niallburkley.com