SlideShare a Scribd company logo
Fighting	with	fat	models
Bogdan	Gusiev
Bogdan	G.
is	9	years	in	IT
6	years	with	Ruby	and	Rails
Long	Run	Rails	Contributor
Some	of	my	gems
http://github.com/bogdan
Datagrid
js-routes
accepts_values_for
furi
My	Blog
http://gusiev.com
http://talkable.com
A	small	startup	is	a	great	place	to	move	from	middle	to
senior	and	above
Fat	Models
Why	the	problem	appears?
All	business	logic	code	goes	to	model	by	default.
In	the	MVC:
Why	it	should	not	be	in	controller	or	view?
Because	they	are	hard	to:
test
maintain
reuse
A	definition	of	being	fat
1000	Lines	of	code
But	it	depends	on:
Docs
Whitespace
Comments
$ wc -l app/models/* | sort -n | tail
532 app/models/incentive.rb
540 app/models/person.rb
544 app/models/visitor_offer.rb
550 app/models/reward.rb
571 app/models/web_hook.rb
786 app/models/site.rb
790 app/models/referral.rb
943 app/models/campaign.rb
998 app/models/offer.rb
14924 total
Existing	techniques
Existing	techniques
Services
Separated	utility	class
Concerns
Modules	that	get	included	to	models
Presenters/Wrappers
Classes	that	wrap	existing	model	to	plug	new	methods
What	do	we	expect?
Standard:
Reusable	code
Easy	to	test
Good	API
Advanced:
Effective	data	model
MORE	features	per	second
Data	Safety
Good	API
Good	API
Is	a	user	connected	to	facebook?
user.connected_to_facebook?
# OR
FacebookService.connected_to_facebook?(user)
# OR
FacebookWrapper.new(user)
.connected_to_facebook?
The	need	of	Services
When	amount	of	utils
that	support	Model	goes	higher
extract	them	to	service	is	good	idea.
Move	class	methods	between	files	is	cheap
# move
(1) User.create_from_facebook
# to
(2) UserService.create_from_facebook
# or
(3) FacebookService.create_user
Organise	services	by	process
rather	than	object	they	operate	on
Otherwise	at	some	moment	UserService	would	not	be	enough
Otherwise	at	some	moment	UserService	would	not	be	enough
The	problem	of	services
Service	is	separated	utility	class.
module CommentService
module CommentService
def self.create(attributes)
comment = Comment.create!(attributes)
deliver_notification(comment)
end
end
"Я	знаю	откуда	что	берется"
Services	don't
provide	default	behavior
provide	default	behavior
The	Need	of	Default	Behavior
Object	should	encapsulate	behavior:
Data	Rules
Set	of	rules	that	a	model	should	fit	at	the	programming
Set	of	rules	that	a	model	should	fit	at	the	programming
level
Ex:	A	comment	should	have	an	author
Business	Rules
Set	of	rules	that	a	model	should	fit	to	exist	in	the	real
world
Ex:	A	comment	should	deliver	an	email	notification
What	is	a	model?
The	model	is	an	imitation	of	real	object
that	reflects	some	it's	behaviors
that	we	are	focused	on.
Wikipedia
Model
is	a	best	place	for	default	behaviour
MVC	authors	meant	that
Implementation
Using	built-in	Rails	features:
ActiveRecord::Callbacks
Hooks	in	models
We	create	default	behavior	and	our	data	is	safe.
Example:	Comment	can	not	be	created	without	notification.
class Comment < AR::Base
after_create :send_notification
end
API	comparison
Comment.create
# or
CommentService.create
Successful	Projects	tend	to	do
one	thing
in	many	different	ways
rather	than	a	lot	of	things
Comment	on	a	web	site
Comment	in	native	mobile	iOS	app
Comment	in	native	mobile	Android	app
Comment	by	replying	to	an	email	letter
Automatically	generate	comments
Team	Growth	Problem
How	would	you	deliver	a	knowledge	that	comment	should
be	made	like	this	to	10	people?
CommentService.create(...)
Reimplement	other	person's	API
has	more	wisdom	than	invent	new	one.
Comment.create(...)
Edge	cases
In	all	cases	data	created	in	regular	way
In	one	edge	cases	special	rules	applied
Service	with	options
module CommentService
def self.create(
attrs, skip_notification = false)
end
Default	behavior
and	edge	cases
Hey	model,	create	my	comment.
Ok
Hey	model,	why	did	you	send	the	notification?
Because	you	didn't	say	you	don't	need	it
Because	you	didn't	say	you	don't	need	it
Hey	model,	create	model	without	notification
Ok
Support	parameter	in	model
class Comment < AR::Base
attr_accessor :skip_comment_notification
after_create do
unless self.skip_comment_notification
send_notification
end
end
end
end
#skip_comment_notification	is	used	only	in	edge	cases.
Default	Behaviour	is	hard	to	make
But	it	solves	communication	problems
that	will	only	increase	over	time
What	is	the	difference?
FacebookService.register_user(...)
Comment.after_create :send_notification
Business	rules:
User	could	be	registered	from	facebook
Comment	should	send	an	email	notification
Model	stands	for	should
Service	stands	for	could
Please	do	not	confuse	should	with	must
Where	are	presenters?
UserPresenter.new(user)
# OR
class User
include UserPresenter
end
Trade	an	API	for	less	methods	in	object
More	effective	presenters?
Example	of	Service	implementation	with	wrapper
More	example	at	ActiveRecord	source	code
class StiTools
def self.run(from_model, to_model)
new(from_model, to_model).perform
end
private
def initialize(from_model, to_model)
def perform
shift_id_info
Datagrid	Gem
Example	of	collection	wrapper
https://github.com/bogdan/datagrid
UsersGrid.new(
last_request: Date.today,
created_at: 1.month.ago..Time.now)
class UsersGrid
scope { User }
filter(:created_at, :date, range: true)
filter(:last_request_at, :datetime, range: true
Wrapping	Data
https://github.com/bogdan/furi
u = Furi.parse(
"http://bogdan.github.com/index.html")
u.subdomain # => 'bogdan'
u.extension # => 'html'
u.ssl? # => false
module Furi
def self.parse(string)
Service	usage	is	inconvinient
because	of	validation
Customer.has_many :purchases
Purchase.has_many :ordered_items
OrderItem.belongs_to :product
ManualOrder.ancestors.include?(
ActiveRecord::Base) # => false
order = ManualOrder.new(attributes)
if order.valid?
order.save_all_those_records_at_once!
Wrappers/Presenters
Very	specific	use
Wrapper	around	collection
Parsing	serialised	object
Under-the-hood	class	inside	a	service
Service	usage	is	inconvinient
The	model	is	still	fat.
What	to	do?
Use	Concerns
Use	Concerns
class Comment < AR::Base
include CommentNotification
include FeedActivityGeneration
include Archivable
end
Rails	default:	app/models/concerns/*
Attention!
Attention!
People	with	high	pressure	or	propensity	to	suicide
Next	slide	can	be	considered	offensive	to	your	religion
Single	Responsibility	Principle
SUCKS
The	proof	follows
There	is	no	a	single	thing
in	the	universe	that	follows	the	SRP
in	the	universe	that	follows	the	SRP
class Proton
include Gravitation
include ElectroMagnetism
include StrongNuclearForce
include WeekNuclearForce
end
Why	man	made	things	should?
Why	man	made	things	should?
The	world	is	unreasonably	complext	to	follow	SRP
How	a	model	that	suppose	to	simulate	those	things
can	have	a	single	responsibility?
It	can't!
Model	Concerns	are	unavoidable
if	you	want	to	have	a	good	model
if	you	want	to	have	a	good	model
Concerns	are	Vertical	slicing
Unlike	MVC	which	is	horizontal	slicing.
Split	model	into	Concerns
class User < AR::Base
class User < AR::Base
include FacebookProfile
end
# Hybrid Concern that provides
# instance and class methods
module FacebookProfile
has_one :facebook_profile # simplified
def connected_to_facebook?
def self.register_from_facebook(attributes)
Ex.1	User	+	Facebook
has_one :facebook_profile	=>	Model
#register_user_from_facebook	=>	Service
#register_user_from_facebook	=>	Service
connect_facebook_profile	=>	Service
connected_to_facebook?	=>	Model
Every	user	should	know	if	it	is	connected	to
facebook	or	not
Ex.2	Deliver	comment	notification
Comment	#send_notification	=>	Model
Default	Behaviour
Even	if	exceptions	exist
Even	if	exceptions	exist
Basic	application	architecture
View
Controller
Model
Model
Services Presenters
Concern Concern Concern
Concerns	Base
Attributes
Associations
has_one
has_one
has_many
has_and_belongs_to_many
But	rarely
Libraries	using	Concerns
ActiveRecord
ActiveModel
Devise
Datagrid
Datagrid
Summary
Inject	Service	between	Model	and
Controller
if	you	need	them
Could?	=>	Service
Should?	=>	Model
SRP	is	a	misleading	principle
It	should	not	inhibit	you	from	having
a	Better	Application	Model
Fat	models	=>	Thin	Concerns
Reimplement	other	person's	API
has	more	wisdom	than	invent	new	one.
Presenters
are	pretty	specific
Use	them	in
Wrapping	the	collection
"private"	class
Service	usage	is	inconvenient
The	End
Thanks	for	your	time
http://gusiev.com
https://github.com/bogdan
Fighting Fat Models (Богдан Гусев)

More Related Content

Fighting Fat Models (Богдан Гусев)