How to stop being Rails Developer
-
Upload
ivan-nemytchenko -
Category
Internet
-
view
228 -
download
5
Transcript of How to stop being Rails Developer
-
WHY LIFE WITH RUBY ON RAILS BECOMESSO HARD AFTER FEW MONTHS OF DEVELOPMENT?
IVAN NEMYTCHENKO
-
IVAN NEMYTCHENKO
> 14 years in IT> cofounded 2 IT-companies
> occasional speaker> coorganized 2 HappyDev conferences
> worked in a startup> worked as project-manager> working with Rails since 2006
> author of RailsHurts.com> internship for ruby junior developers:
SkillGrid
> twitter: @inem
-
WHAT IS WRONG WITH RAILS?
-
SAME PATTERN
-
RAILSHURTS.COM/MESS
-
RAILS-WAY IS NOT ENOUGH
-
RAILS WORLD is so coolthat we don't even have whole class of problems
millions of java programmersstruggring every day
-
THESE THINGS MIGHT HELP YOU
> SOLID principles> Design Patterns
> Refactoring techniques> Architecture types
> Code smells identification> Best practices of testing
-
PRINCIPLES.MISUNDERSTOOD.
APPLIED.
-
DRYDON'T REPEAT YOURSELF
RAILSHURTS.COM/_DRY
-
JUST AVOID DUPLICATION, RIGHT?
-
EVERYTHING HAS ITS PRICE
-
PRICE: MORE RELATIONS
-
Duplication is far cheaper that wrong abstraction
Sandi Metz
-
KISSKEEP IT SIMPLE, STUPID
RAILSHURTS.COM/_KISS
-
RAILS IS SIMPLE, RIGHT?
-
ACTIVERECORDclass User < ActiveRecord::Base
end
-
ACTIVERECORD
-
ACTIVERECORD
> input data coercion> setting default values> input data validation
> interaction with the database> handling nested structures
> callbacks (before_save, etc...)
-
- WHY SHOULD I CARE?
-
Polymorphic STI model which belongs to another polymorphic model through third model, which also has
some valuable JSON data stored in Postgres using hstore.
-
WHAT ARE YOU GONNA DO?> reorganize associations
> become Rails core contributor
-
IT IS GOING TO BE PAINFUL
-
PAINFUL BECAUSE OF THE COMPLEXITY
-
RAILS IS NOT SIMPLE. IT IS CONVENIENT.
-
FAT MODEL,SKINNY CONTROLLER
RAILSHURTS.COM/_SKINNY
-
RIGHT PROBLEM IDENTIFIED
-
BUT IT IS ONLY PART OF THE PROBLEM
-
YOU SHOULD HAVE NO FAT CLASSES AT ALL
-
*HINT:
PROPER SOLUTIONREQUIRES THINKINGOUT OF MVC BOX
-
RAILSIS NOT YOURAPPLICATION
RAILSHURTS.COM/_APP
-
It just doesn't make sense. Here's my Rails application.
Here's app folder. What's wrong?
-
RAILSHURTS.COM/_CLEAN
-
HEXXAGONAL ARCHITECTURE
RAILSHURTS.COM/_HEXX
-
YAGNIYOU AREN'T GONNA NEED IT
RAILSHURTS.COM/_YAGNI
-
WHAT IF YOUR APPDOESN'T NEED PERSISTANCE YET?
-
WHAT IF YOUR APP COREDOESN'T NEED WEB FRAMEWORK YET?
-
Question everything generally thought to be obvious
Dieter Rams
-
THREE RULES FOR DEVELOPERSWHO ARE NOT SURE OF ONE'S STRENGTH
> Do not apply DRY principle too early> Remember about Single Responsibility Principle
> Learn how to apply Design Patterns
-
LET'S GET OUR HANDS DIRTY!
-
FEATURE #1: USERS REGISTRATION
-
FEATURE #1: USERS REGISTRATION
-
SINGLE TABLE INHERITANCE !
CONDITIONAL VALIDATIONS !
-
BOTH STI AND CONDITIONAL VALIDATIONS ARE JUST WORKAROUNDS
THE PROBLEM IS DEEPER!
-
SINGE RESPONSIBILITY PRINCIPLE:
A CLASS SHOULD HAVE ONLY ONE REASON TO
CHANGE
-
OUR MODEL KNOWS ABOUT HOW..
> admin form is validated> org_user form is validated
> guest_user form is validated> user data is saved
-
FORM OBJECT !
-
COOKING FORM OBJECT (STEP 1)class Person attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name, @last_name = first_name, last_name endend
-
COOKING FORM OBJECT (STEP 2)class Person attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name, @last_name = first_name, last_name end
include ActiveModel::Validations validates_presence_of :first_name, :last_nameend
-
COOKING FORM OBJECT (STEP 3)class Person include Virtus.model attribute :first_name, String attribute :last_name, String
include ActiveModel::Validations validates_presence_of :first_name, :last_name end
-
FORM OBJECTclass OrgUserInput include Virtus.model include ActiveModel::Validations
attribute :login, String attribute :password, String attribute :password_confirmation, String attribute :organization_id, Integer
validates_presence_of :login, :password, :password_confirmation validates_numericality_of :organization_idend
-
USING FORM OBJECTdef create input = OrgUserInput.new(params) if input.valid? @user = User.create(input.to_hash) else #... endend
-
FOUR SIMPLE OBJECTSINSTEAD OF ONE COMPLEX
-
FEATURE #2: BONUSCODE REDEEM
-
def redeem unless bonuscode = Bonuscode.find_by_hash(params[:code]) render json: {error: 'Bonuscode not found'}, status: 404 and return end if bonuscode.used? render json: {error: 'Bonuscode is already used'}, status: 404 and return end unless recipient = User.find_by_id(params[:receptor_id]) render json: {error: 'Recipient not found'}, status: 404 and return end
ActiveRecord::Base.transaction do amount = bonuscode.mark_as_used!(params[:receptor_id]) recipient.increase_balance!(amount)
if recipient.save && bonuscode.save render json: {balance: recipient.balance}, status: 200 else render json: {error: 'Error during transaction'}, status: 500 end end end
-
SINGLERESPONSIBILITY
PRINCIPLE
-
bonuscode.redeem_by(user)
ORuser.redeem_bonus(code)
?
-
SERVICE OBJECT!(USE CASE, INTERACTOR)
-
COOKING SERVICE OBJECT (STEP 1) class RedeemBonuscode def redeem unless bonuscode = Bonuscode.find_by_hash(params[:code]) render json: {error: 'Bonuscode not found'}, status: 404 and return end if bonuscode.used? render json: {error: 'Bonuscode is already used'}, status: 404 and return end unless recipient = User.find_by_id(params[:receptor_id]) render json: {error: 'Recipient not found'}, status: 404 and return end
ActiveRecord::Base.transaction do amount = bonuscode.mark_as_used!(params[:receptor_id]) recipient.increase_balance!(amount)
if recipient.save && bonuscode.save render json: {balance: recipient.balance}, status: 200 else render json: {error: 'Error during transaction'}, status: 500 end end end end
-
COOKING SERVICE OBJECT (STEP 2) class RedeemBonuscode def run!(params) unless bonuscode = Bonuscode.find_by_hash(params[:code]) raise BonuscodeNotFound.new end if bonuscode.used? raise BonuscodeIsAlreadyUsed.new end unless recipient = User.find_by_id(params[:receptor_id]) raise RecipientNotFound.new end
ActiveRecord::Base.transaction do amount = bonuscode.mark_as_used!(params[:receptor_id]) recipient.increase_balance!(amount) recipient.save! && bonuscode.save! end recipient.balance end end
-
COOKING SERVICE OBJECT (STEP 3)def redeem use_case = RedeemBonuscode.new
begin recipient_balance = use_case.run!(params) rescue BonuscodeNotFound, BonuscodeIsAlreadyUsed, RecipientNotFound => ex render json: {error: ex.message}, status: 404 and return rescue TransactionError => ex render json: {error: ex.message}, status: 500 and return end
render json: {balance: recipient_balance}end
-
BUSINESS LOGIC /CONTROLLER SEPARATION
-
7 Patterns to Refactor Fat ActiveRecord Models railshurts.com/_7ways
* * *Arkency
railshurts.com/_arkency* * *
Adam Hawkins railshurts.com/_hawkins