Slide 1

Slide 1 text

Not the Rails Way. Again! Igor Alexandrov, JetRockets Co-Founder

Slide 2

Slide 2 text

Igor Alexandrov github.com/igor-alexandrov twitter.com/igor_alexandrov instagram.com/igor_alexandrov

Slide 3

Slide 3 text

Tver.IO 11 meetups in 2019 > 80 people on each meetup Speakers from Evrone, Evil Martians, RAWG.io, Yandex, Mail.RU Audience > 2000 people • • • •

Slide 4

Slide 4 text

About since 2008 >= Rails 2.1 SmallTalk => Ruby => Crystal JetRockets CTO • • • •

Slide 5

Slide 5 text

JetRockets

Slide 6

Slide 6 text

The Rails Way?

Slide 7

Slide 7 text

The Rails Way

Slide 8

Slide 8 text

The Rails Way (Components) the champion of the startup hustle; ecosystem; community; • • •

Slide 9

Slide 9 text

Ecosystem acts_as_taggable •

Slide 10

Slide 10 text

Ecosystem acts_as_taggable acts_as_tree • •

Slide 11

Slide 11 text

Ecosystem acts_as_taggable acts_as_tree acts_as_commentable • • •

Slide 12

Slide 12 text

Ecosystem acts_as_taggable acts_as_tree acts_as_commentable acts_as_shopping_cart • • • •

Slide 13

Slide 13 text

Ecosystem acts_as_taggable acts_as_tree acts_as_commentable acts_as_shopping_cart … • • • • •

Slide 14

Slide 14 text

Community. How it was born? (2007) everyone came from somewhere (SmallTalk, Java, PHP); •

Slide 15

Slide 15 text

Community. How it was born? (2007) everyone came from somewhere (SmallTalk, Java, PHP); no developers who “were born with Rails”; • •

Slide 16

Slide 16 text

Community. How it was born? (2007) everyone came from somewhere (SmallTalk, Java, PHP); no developers who “were born with Rails”; we needed to be unique and have something to unite us; • • •

Slide 17

Slide 17 text

Community. How it was born? (2007) everyone came from somewhere (SmallTalk, Java, PHP); no developers who “were born with Rails”; we needed to be unique and have something to unite us; say “no” to “the architecture astronauts Java way” or the “PHP spaghetti way”; • • • •

Slide 18

Slide 18 text

Community. How it was born? (2007) “no” to everything Java-like; •

Slide 19

Slide 19 text

Community. How it was born? (2007) “no” to everything Java-like; XML → YAML; • •

Slide 20

Slide 20 text

Community. How it was born? (2007) “no” to everything Java-like; XML → YAML; patterns → ; • • •

Slide 21

Slide 21 text

Community. How it was born? (2007) “no” to everything Java-like; XML → YAML; patterns → ; DDD movement → ; • • • •

Slide 22

Slide 22 text

We’ve got ActiveRecord. We take the object from the database row and use it in all the three layers. Fat models or fat controllers? Whatever, let’s just not create new layers. » «

Slide 23

Slide 23 text

Success!

Slide 24

Slide 24 text

What we had in The Rails Way? ActiveRecord objects everywhere, including the views; •

Slide 25

Slide 25 text

What we had in The Rails Way? ActiveRecord objects everywhere, including the views; external gems used for most of the features; • •

Slide 26

Slide 26 text

What we had in The Rails Way? ActiveRecord objects everywhere, including the views; external gems used for most of the features; non-trivial logic with the combination of filters, callbacks, conditional validations and tons of ; • • •

Slide 27

Slide 27 text

What we had in The Rails Way? ActiveRecord objects everywhere, including the views; external gems used for most of the features; non-trivial logic with the combination of filters, callbacks, conditional validations and tons of ; metaprogramming; • • • •

Slide 28

Slide 28 text

What we had in The Rails Way? ActiveRecord objects everywhere, including the views; external gems used for most of the features; non-trivial logic with the combination of filters, callbacks, conditional validations and tons of ; metaprogramming; only 3 layers - Models, Views, Controllers. • • • • •

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Let's dive deeper

Slide 35

Slide 35 text

619 729 Fat Models # app/models/document.rb # Callbacks 621 622 before_validation :on => :create do 623 self.scorer = applicant 624 self.closing_status ||= 'likely' # … 728 # Class Methods

Slide 36

Slide 36 text

Logic in Views # app/views/shared/header.html.erb <% if current_user %> <%= current_user.name %> <% else %> Guest <% end %>

Slide 37

Slide 37 text

Gemfile Hell $ wc -l Gemfile 390 Gemfile

Slide 38

Slide 38 text

users_comments comments.select == params[:username] Controllers Hell class CommentsController < ApplicationController def posts = Post.all comments = posts.map(&:comments).flatten @user_comments = do |comment| comment.author.username end end end

Slide 39

Slide 39 text

Rails Helpers Retrieve data from the DB visible_comments_for(@article) ; •

Slide 40

Slide 40 text

Rails Helpers Retrieve data from the DB visible_comments_for(@article) ; no dependency tracking; • •

Slide 41

Slide 41 text

Rails Helpers Retrieve data from the DB visible_comments_for(@article) ; no dependency tracking; no inheritance; • • •

Slide 42

Slide 42 text

Rails Helpers Retrieve data from the DB visible_comments_for(@article) ; no dependency tracking; no inheritance; html tags generation. • • • •

Slide 43

Slide 43 text

Everything is so bad?

Slide 44

Slide 44 text

How do we live with this?

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

Standartization

Slide 47

Slide 47 text

Standartization Architecture •

Slide 48

Slide 48 text

Standartization Architecture Code Style • •

Slide 49

Slide 49 text

Standartization Architecture Code Style Libraries & workflow • • •

Slide 50

Slide 50 text

Architecture

Slide 51

Slide 51 text

TrailBlazer

Slide 52

Slide 52 text

TrailBlazer

Slide 53

Slide 53 text

Dry-rb

Slide 54

Slide 54 text

Query Objects Scope that interacts with more than one column and/or joins in other tables; •

Slide 55

Slide 55 text

Query Objects Scope that interacts with more than one column and/or joins in other tables; have single interface #call(relation) ; • •

Slide 56

Slide 56 text

Query Objects Scope that interacts with more than one column and/or joins in other tables; have single interface #call(relation) ; return ActiveRecord::Relation ; • • •

Slide 57

Slide 57 text

Query Objects Scope that interacts with more than one column and/or joins in other tables; have single interface #call(relation) ; return ActiveRecord::Relation ; accept query params as constructor arguments. • • • •

Slide 58

Slide 58 text

reduce_loan_officer_id if loan_officer_id Query Objects require 'dry-initializer' class Crm::ApplicationsSearch extend Dry::Initializer option :loan_officer_id, optional: true option # ... def call(application_relation) @query = application_relation @query = # ... @query end end

Slide 59

Slide 59 text

Service Objects

Slide 60

Slide 60 text

Web application — chain of services Request [*] [*] [*] [*] Response • • • • • •

Slide 61

Slide 61 text

Web application — chain of services Request Nginx Application Server (Unicorn/Puma) Rails Application DB Response • • • • • •

Slide 62

Slide 62 text

Web application — chain of services Request Nginx Application Server (Unicorn/Puma) Rails Application DB Response • • • • • •

Slide 63

Slide 63 text

Rails application — chain of services

Slide 64

Slide 64 text

Service Objects responsibilities Place to define complex actions; processes multiple steps and interactions; remove callbacks from ActiveRecord models; place to define validations. • • • •

Slide 65

Slide 65 text

Validator.check(contact).bind do |contact| Service Objects (AR validators) class Contacts::CreateContactFromDeal < BaseService def call(deal) contact = something_that_creates_contact(deal) contact.save! Success(contact) end end class Validator < ::ApplicationValidator validates :email, presence: true end end

Slide 66

Slide 66 text

Service Objects (dry-rb) class Leads::CreateLead # … def call(lead, params) params = yield validate(lead, params) lead = yield persist(lead, params) yield notify(lead) Success(lead) end # … end

Slide 67

Slide 67 text

yield Success(lead) Service Objects (dry-monads) class Leads::CreateLead # … def call(lead, params) params = validate(lead, params) lead = yield persist(lead, params) yield notify(lead) end # … end

Slide 68

Slide 68 text

Service Objects (dry-validation)

Slide 69

Slide 69 text

dry-validation Dry-schema to define reusable schemas; input sanitization, coercion and type-check; macros (beta); custom rules that don't break your head. • • • •

Slide 70

Slide 70 text

detect_contract(params[:source]).call(params) Service Objects (dry-validation) class Leads::CreateLead def call(lead, params) # … end def validate(lead, params) result = if result.success? Success(result.to_h) else Failure(…) end end end

Slide 71

Slide 71 text

ContactsContract HireUsContract Service Objects (dry-validation) class Leads::CreateLead # … class < Dry::Validation::Contract # … end class < Dry::Validation::Contract # … end end

Slide 72

Slide 72 text

case result Service Objects (controllers) class LeadsController < ApplicationController def create command = Lead::CreateLead.new result = command.(resource_lead, params[:lead]) when Dry::Monads::Success render #… when Dry::Monads.Failure(JetRockets::ValidationError) render #… when Dry::Monads.Failure render #… end end end

Slide 73

Slide 73 text

Service Objects (RSpec)

Slide 74

Slide 74 text

HireUsContract PostsContract Service Objects (RSpec) RSpec.describe Leads::CreateLead:: do subject(:command) do Leads::CreateLead::::HireUsContract.new end # operations end RSpec.describe Leads::CreateLead:: do # … end

Slide 75

Slide 75 text

Service Objects conventions Avoid NounServices (e.g. UsersService, ProductService ); •

Slide 76

Slide 76 text

Service Objects conventions Avoid NounServices (e.g. UsersService, ProductService ); be specific (e.g. Users::ForgotPassword ); • •

Slide 77

Slide 77 text

Service Objects conventions Avoid NounServices (e.g. UsersService, ProductService ); be specific (e.g. Users::ForgotPassword ); use namespaces based on domain concepts (e.g. /crm/documents/category/update_category ); • • •

Slide 78

Slide 78 text

Service Objects conventions Avoid NounServices (e.g. UsersService, ProductService ); be specific (e.g. Users::ForgotPassword ); use namespaces based on domain concepts (e.g. /crm/documents/category/update_category ); focus on readability of your call method; • • • •

Slide 79

Slide 79 text

Service Objects conventions Avoid NounServices (e.g. UsersService, ProductService ); be specific (e.g. Users::ForgotPassword ); use namespaces based on domain concepts (e.g. /crm/documents/category/update_category ); focus on readability of your call method; return results with state. • • • • •

Slide 80

Slide 80 text

Schema

Slide 81

Slide 81 text

Code Style

Slide 82

Slide 82 text

Code Style

Slide 83

Slide 83 text

Code Style

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

github.com/testdouble/standard

Slide 86

Slide 86 text

standard inherit_gem: standard: config/base.yml AllCops: Exclude: - 'bin/*' - 'tmp/**/*' …

Slide 87

Slide 87 text

standard Wrapper on top of RuboCop; can be used separately; can included into Rubocop configuration; includes only rubocop and rubocop-performance Cops configuration. • • • •

Slide 88

Slide 88 text

What to do with other Cops? rubocop-rails; rubocop-rspec; rubocop-i18n; • • •

Slide 89

Slide 89 text

Create your own gem with configuration

Slide 90

Slide 90 text

jetrockets-standard https:/ /github.com/jetrockets/jetrockets-standard https:/ /rubygems.org/gems/jetrockets-standard Also created similar packages for JS (ESLint, prettier) • • •

Slide 91

Slide 91 text

Company-wide "whitelist" of gems

Slide 92

Slide 92 text

Company-wide "whitelist" of gems AuthLogic Devise ActiveAdmin • • •

Slide 93

Slide 93 text

TrailBlazer?

Slide 94

Slide 94 text

No, it is still Rails With modular approach; •

Slide 95

Slide 95 text

No, it is still Rails With modular approach; with composition of services; • •

Slide 96

Slide 96 text

No, it is still Rails With modular approach; with composition of services; mature and stable; • • •

Slide 97

Slide 97 text

No, it is still Rails With modular approach; with composition of services; mature and stable; maintainable. • • • •

Slide 98

Slide 98 text

What to do right now? implement company/project-wide standard; •

Slide 99

Slide 99 text

What to do right now? implement company/project-wide standard; follow these standards; • •

Slide 100

Slide 100 text

What to do right now? implement company/project-wide standard; follow these standards; don't be afraid to experiment; • • •

Slide 101

Slide 101 text

What to do right now? implement company/project-wide standard; follow these standards; don't be afraid to experiment; start new project with Rails! • • • •

Slide 102

Slide 102 text

Books

Slide 103

Slide 103 text

Books

Slide 104

Slide 104 text

Links jetrockets.pro github.com/jetrockets github.com/igor-alexandrov