• No results found

Growing Rails

N/A
N/A
Protected

Academic year: 2021

Share "Growing Rails"

Copied!
88
0
0

Loading.... (view fulltext now)

Full text

(1)
(2)

Structure large Ruby on Rails apps with the tools you

already know and love

Henning Koch and Thomas Eisenbarth

This book is for sale athttp://leanpub.com/growing-rails This version was published on 2014-10-10

This is aLeanpubbook. Leanpub empowers authors and publishers with the Lean Publishing process.Lean Publishingis the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do.

(3)

1. Introduction . . . . 1

How we got here . . . 1

The myth of the infinitely scalable architecture . . . 1

How we structured this book . . . 2

New rules for Rails

. . .

4

2. Beautiful controllers . . . . 5

The case for consistent controller design . . . 5

Normalizing user interactions . . . 6

A better controller implementation . . . 6

Why have controllers at all? . . . 9

3. Relearning ActiveRecord . . . 12

Understanding the ActiveRecord lifecycle . . . 13

The true API of ActiveRecord models . . . 15

4. User interactions without a database . . . 17

Writing a better sign in form . . . 17

Building PlainModel . . . 21

Refactoring controllers from hell . . . 22

Creating a system for growth

. . .

29

5. Dealing with fat models . . . 30

Why models grow fat . . . 30

The case of the missing classes . . . 32

Getting into a habit of organizing . . . 33

6. A home for interaction-specific code . . . 34

A modest approach to form models . . . 35

More convenience for form models . . . 38

(4)

Example . . . 40

Did we just move code around? . . . 43

8. Organizing large codebases with namespaces . . . 44

Real-world example . . . 45

Use the same structure everywhere . . . 47

9. Taming stylesheets . . . 50

How CSS grows out of control . . . 50

An API for your stylesheets . . . 55

The BEM prime directive . . . 58

Full BEM layout example . . . 60

Organizing stylesheets . . . 64

BEM anti-patterns . . . 65

Living style guides . . . 65

Pragmatic BEM . . . 67

Building applications to last

. . .

69

10.On following fashions . . . 70

Before/after code comparisons . . . 70

Understanding trade-offs . . . 70

The value of consistency . . . 71

11.Surviving the upgrade pace of Rails . . . 72

Gems increase the cost of upgrades . . . 72

Upgrades are when you pay for monkey patches . . . 72

Don’t live on the bleeding edge . . . 73

12.Owning your stack . . . 74

Accepting storage services into your stack . . . 75

Maxing out your current toolbox . . . 75

13.The value of tests . . . 77

Choosing test types effectively . . . 77

How many tests are too many? . . . 79

When to repeat yourself in tests - and when not to . . . 80

Better design guided by tests . . . 80

Getting started with tests in legacy applications . . . 81

Overcoming resistance to testing in your team . . . 81

(5)

In this book we demonstrate low-ceremony techniques to scale large, monolithic Rails applications. Instead of introducing new patterns or service-oriented architecture, we will show how to use discipline, consistency and code organization to make your code grow more gently. Instead of using new gems we will use the tools built into Rails to accomplish our goal. The tools you already know and love.

How we got here

When you started working with Rails some years ago, it all seemed so easy. You saw the blog-in-ten-minutes video. You reproduced the result. ActiveRecord felt great and everything had its place. Fast forward two years. Your blog is now a full-blown CMS with a hundred models and controllers. Your team has grown to four developers. Every change to the application is a pain. Your code feels like a house of cards.

You turn to the internet for assistance, and find it filled with silver bullets. You should move away from fat controllers, it says. But do avoid fat models. And use DCI. Or CQRS. Or SOA. As you cycle through patterns, your application is becoming a patchwork of different coding techniques. New team members are having a hard time catching up. And you are beginning to question if all those new techniques actually help, or if you are just adding layers of indirection.

You start missing the early days, when everything had seemed so easy and every new piece of code had its place. You actually liked ActiveRecord before it drowned you in a sea of callbacks. If only there was a way to do things “the Rails way” without it falling apart as your application grows.

The myth of the infinitely scalable architecture

We would like to show you one path to write Rails apps that are a joy to understand and change, even as your team and codebase grows. This book describes a complete toolbox that has served us well for all requirements that we have encountered.

Before we do that we need to let you in on an inconvenient secret: Large applications are large. The optimal implementation of a large application will always be more complex than the optimal representation of a smaller app. We cannot make this go away. What we can do is to organize a codebase in a way that “scales logarithmically”. Twice as many models should not mean twice as many problems.

(6)

Vanilla Rails vs. Structured Rails

In order to achieve the green line in the chart above, you do not necessarily need to change the way your application is built. You do not necessarily need to introduce revolutionary architectures to your code. You can probably make it with the tools built into Rails, if you use them in a smarter way.

Compare this to sorting algorithms. When a sorting function is too slow, your first thought is very likely not “we should install a Hadoop cluster”. Instead you simply look for an algorithm that scales better and go the Hadoop way when you become Amazon.

In a similar fashion this book is not about revolutionary design patterns or magic gems that make all your problems go away. Instead, we will show how to use discipline, consistency and organization to make your application grow more gently.

How we structured this book

We divided the book into three parts:

Part I:

New rules for Rails

In this part we unlearn bad Rails habits and introduce design conventions for controllers and user-facing models. By being consistent in our design decisions we can make it easier to navigate and understand our application even as its codebase grows.

The first part contains the following chapters: • Beautiful controllers

• Relearning ActiveRecord

(7)

Part II:

Creating a system for growth

As we implement more and more requirements, all that code has to go somewhere. If all we do is add more lines to existing classes and methods, we end up with an unmaintainable mess.

In this part we show how to organize code in a way that encourages the creation of new classes which in turn enhances comprehensibility and maintainability. We also lean to contain the side effects that code can have on unrelated parts of our application.

The second part discusses the following topics: • Dealing with fat models

• A home for interaction-specific code • Extracting service objects

• Organizing large codebases with namespaces • Taming stylesheets

Part III:

Building applications to last

We show how to think about future maintenance when making decisions today. We make a case for adopting new technologies and patterns with care, and for taking full responsibility for those techniques and technologies that you do choose to adopt.

The last part contains the following chapters: • On following fashions

• Surviving the upgrade pace of Rails • Owning your stack

(8)
(9)

Let’s talk about controllers. Nobody loves their controllers.

When a developer learns about MVC for the first time, she will quickly understand the purpose of models and views. But working with controllers remains awkward:

• It is hard to decide whether a new bit of functionality should go into your controller or into your model. Should the model send a notification e-mail or is that the controller’s responsibility? Where to put support code that handles the differences between model and user interface?

• Implementing custom mappings between a model and a screen requires too much controller code. Examples for this are actions that operate on multiple models, or a form that has additional fields not contained in the model. It is too cumbersome to support basic interactions like a “form roundtrip”, where an invalid form is displayed again with the offending fields highlighted in red.

• Any kind of code put into a controller might as well sit behind a glass wall. You can see it, but it is hard to test and experiment with it. Running controller code requires a complex environment (request, params, sessions, etc.) which Rails must conjure for you. This makes controllers an inaccessible habitat for any kind of code.

• Lacking clear guidelines for designing controllers, no two controllers are alike. This makes working on existing UI a chore, since you have to understand how data flows through each individual controller.

We cannot make controllers go away. However, by following a few simple guidelines we can reduce the importance of controllers in our application and move controller code to a better place. Because the less business logic is buried inside controllers, the better.

The case for consistent controller design

Ruby on Rails has few conventions for designing a controller class. There are no rules how to instantiate model classes or how we deal with input errors. The result is acontrollersfolder where

every class works a little differently.

This approach does not scale. When every controller follows a different design, a developer needs to learn a new micro API for every UI interaction she needs to change.

(10)

This reduces the mental overhead required to navigate through a large Rails application and understand what is happening. If you knew the layout of a controller class before you even open the file, you could focus on models and views. That is our goal.

Having a default design approach also speeds up development of new controllers by removing implementation decisions. You always decide to use CRUD and quickly move on to the parts of your application you really care about: Your models and views.

This point becomes more important as your team grows, or once your application becomes too large to fit into your head entirely.

Normalizing user interactions

How to come up with a default controller design when your application has many different kinds of user interactions? The pattern we use is to reduce every user interaction to aRails CRUD resource¹. We employ this mapping even if the user interface is not necessarily a typical CRUD interface at first glance.

Even interactions that do not look like plain old CRUD resources can be modeled as such. A screen to cancel a subscription can be thought of as destroying a subscription or creating a new cancellation. A screen to upload multiple images at once can be seen as creating an image batch (even if there is noImageBatchmodel).

By normalizing every user interaction to a CRUD interaction, we can design a beautiful controller layout and reuse it again and again with little changes.

A better controller implementation

When we take over maintenance for existing Rails projects, we often find unloved controllers, where awkward glue code has been paved over and over again, negotiating between request and model in the most unholy of protocols.

It does not have to be that way. We believe that controllers deserve better:

• Controllers should receive the same amount of programming discipline as any other type of class. They should be short,DRY²and easy to read.

• Controllers should provide the minimum amount of glue code to negotiate between request and model.

• Unless there are good reasons against it, controllers should be built against a standard, proven implementation blueprint.

¹http://guides.rubyonrails.org/routing.html#resource-routing-the-rails-default ²http://en.wikipedia.org/wiki/Don’t_repeat_yourself

(11)

What is a controller implementation that is so good you want to use it over and over again? Of course Rails has always included a scaffoldscript that generates controller code. Unfortunately

that generated controller is unnecessarily verbose and not DRY at all.

Instead we use the following standard implementation for a controller that CRUDs an ActiveRecord (or ActiveModel) class:

1 class NotesController < ApplicationController 2 3 def index 4 load_notes 5 end 6 7 def show 8 load_note 9 end 10 11 def new 12 build_note 13 end 14 15 def create 16 build_note

17 save_note or render 'new' 18 end 19 20 def edit 21 load_note 22 build_note 23 end 24 25 def update 26 load_note 27 build_note

28 save_note or render 'edit' 29 end 30 31 def destroy 32 load_note 33 @note.destroy 34 redirect_to notes_path 35 end 36

(12)

37 private 38

39 def load_notes

40 @notes ||= note_scope.to_a 41 end

42

43 def load_note

44 @note ||= note_scope.find(params[:id]) 45 end

46

47 def build_note

48 @note ||= note_scope.build 49 @note.attributes = note_params 50 end 51 52 def save_note 53 if @note.save 54 redirect_to @note 55 end 56 end 57 58 def note_params

59 note_params = params[:note]

60 note_params ? note_params.permit(:title, :text, :published) : {} 61 end 62 63 def note_scope 64 Note.scoped 65 end 66 67 end

Note a couple of things about the code above:

• The controller actions are delegating most of their work to helper methods likeload_noteor build_note. This allows us to not repeat ourselves and is a great way to adapt the behavior

of multiple controller actions by changing a single helper method. For example if you want to place some restriction on how objects are created, you probably want to apply the same restriction on how objects are updated. It also facilitates the DRY implementation of custom controller actions (like asearchaction, not visible in the example).

• There is a private methodnote_scopewhich is used by all member actions (show,edit,update,

(13)

Note how at no point does an action talk to theNote model directly. By havingnote_scope

guard access to theNotemodel, we have a central place to control which records this controller

can show, list or change. This is a great technique to e.g. implement authorization schemes³ where access often depends on the current user.

• There is a private methodnote_paramsthat returns the attributes that can be set through the updateand create actions. Note how that method usesstrong parameters⁴ to whitelist the

attributes that the user is allowed to change. This way we do not accidentally allow changing sensitive attributes, such as foreign keys or admin flags. Strong parameters are available in Rails 4+. If you are on Rails 3 you can use thestrong_parameters gem⁵ instead. And if you are onRails 2 LTS⁶ you can useHash#slice⁷to a similar effect. In any case we recommend such an approach in lieu of theattr_accessiblepattern that used to be the default in older

Rails versions. The reason is that authorization does not belong into the model, and it is really annoying to have attribute whitelisting get in your way when there is not even a remote user to protect from (e.g. the console, scripts, or background jobs).

• Every controller action reads or changes a single model. Even if an update involves multiple models, the job of finding and changing the involved records should be pushed to an orchestrating model. You can do so withnested forms⁸or form models (which we will learn about ina later chapter). By moving glue code from the controller into the model it becomes easier to test and reuse.

• Although the controller from the code example maps directly to an ActiveRecord model called

Note, this is by no means a requirement. For instance, you might want to use a custom model

for forms that are complicated or do not persist to a database. This book will equip you with various techniquesfor providing a mapping between a user interface and your core domain models.

Why have controllers at all?

We advocate a very simple and consistent controller design that pushes a lot of code into the model. One might ask: Why have controllers at all? Can’t we conjure some Ruby magic that automatically maps requests onto our model?

Well, no. There are still several responsibilities left that controllers should handle: • Security (authentication, authorization)

• Parsing and white-listing parameters • Loading or instantiating the model

³Also see our talk:Solving bizarre authorization requirements with Rails. ⁴http://api.rubyonrails.org/classes/ActionController/StrongParameters.html ⁵https://github.com/rails/strong_parameters

⁶https://railslts.com

⁷http://apidock.com/rails/Hash/slice

(14)

• Deciding which view to render

That code needs to go somewhere and the controller is the right place for it.

However, a controller never does the heavy lifting. Controllers should contain the minimum amount of glue to translate between the request, your model and the response.

We will learn techniques to extract glue code into classes in “User interactions without a database” and “A home for interaction-specific code”.

But before we do that, we need to talk about ActiveRecord.

..

A note on controller abstractions

There are gems likeInherited ResourcesorResource Controllerthat generate a uniform controller implementation for you. For instance the following code would give you a fully implemented

UsersControllerwith theseven RESTful default actionswith a single line of code:

1 class UsersController < ResourceController::Base

2 end

When Ruby loads theUsersControllerclass, Resource Controller would dynamically generate a

default implementationfor your controller actions.

Following the idea of convention over configuration, one would reconfigure the default implemen-tation only if needed:

1 class UsersController < ResourceController::Base

2

3 create.after do

4 Mailer.welcome(@user).deliver

5 end

6 7 end

We used to like this idea a lot. However, having used it in several large projects, we now prefer to write out controllers manually again. Here are some reasons why we no longer like to use

resource_controllerand friends:

(15)

..

Many things that have a natural place in a hand-written controller are awkward to write in a controller abstraction. For example using a different model for new/createand edit/updateis

almost impossible to implement using the configuration options of Resource Controller.

Too much magic

We rotate on projects a lot. Often new developers on projects using gems such as InheritedResources have a hard time understanding what is happening: Which methods are generated automatically? How do I disable them if not all of them are necessary? Which configuration options do exist? At the end of the day, we saw colleagues spending more time reading documentation than writing code. It’s simply too much magic, too much implicit behavior.

Controller abstractions can be useful if you know their pros and cons. We are using them only in very simple CRUD apps with extremely uniform user interfaces.

https://github.com/josevalim/inherited_resources https://github.com/makandra/resource_controller

http://guides.rubyonrails.org/routing.html#resource-routing-the-rails-default

(16)

Developers burnt by large Rails applications often blame their pain on ActiveRecord. We feel that a lof of this criticism is misplaced. ActiveRecord can be a highly effective way to implement user-facing models, meaning models that back an interaction with a human user.

An example for a user-facing model is a sign up form that takes a username and password, informs the user if some data is invalid, and only creates a user once when all the information is valid. Since humans are not perfect, a user-facing model needs to revolve around dealing with broken input data. It should make it easy to implement things like the following:

• Validation of data entered by a user (e.g. a username must not be taken, a password must have a minimum number of characters)

• Form roundtrips: When any input field contains invalid data, the form is displayed again with the invalid fields being highlighted. During a roundtrip all input fields retain their values. Only when everything is valid an action is performed (like creating a record).

• Lifecycle callbacks: We want to run code at different points during the human-model-interaction, e.g. do something during the data validation or send an e-mail after successful submission.

A form roundtrip after a validation error

For this type of model, ActiveRecord can be a great choice. ActiveRecord is focused around error handling and input validation. It allows you to collect and process possibly broken user input with a minimum amount of code in controllers and views.

(17)

Unfortunately, ActiveRecord also comes with plenty of opportunities to shoot yourself in the foot. When you do not model your classes in the way ActiveRecord wants you to, you will find yourself fighting against the library instead of leveraging its power.

Understanding the ActiveRecord lifecycle

The insidious part about ActiveRecord is that it pretends to get out of your way and let you write a simple Ruby class with methods of your choice. The reality however is that ActiveRecord requires your models to be written in a certain style in order to be effective.

Say you have anInvitemodel. Invites are created without an associatedUser, and can be accepted

later. When an invite is accepted, the current user is assigned and aMembershipis created. So we

provide anaccept!method that does all of that: 1 class Invite < ActiveRecord::Base

2

3 belongs_to :user

4

5 def accept!(user) 6 self.user = user 7 self.accepted = true

8 Membership.create!(:user => user)

9 save!

10 end 11

12 end

The problem with the code above is that there are a dozen ways to circumvent theaccept!method

by setting theuseroracceptedattribute with one of ActiveRecord’s many auto-generated methods.

For instance, you cannot prevent other code from simply changing theacceptedflag without going

throughaccept!:

1 invite = Invite.find(...) 2 invite.accepted = true 3 invite.save!

That is unfortunate, since now we have an Inviterecord in a strange state: It is accepted, but no

user was assigned and the membership record is missing. There are many other ways to accidentally create such an invite, e.g.:

(18)

• invite[:accepted] = true; invite.save

• Invite.new(accepted: true).save

• Invite.create!(accepted: true)

InObjects on Rails¹Avdi Grim called this the “infinite protocol” of ActiveRecord.

So what does this mean for ourInviteclass? We do not want other code to violate data integrity

by accidentally calling the wrong method. After all one of the most basic OOP principles is that it should be very hard to misuse a class.

Make it hard to misuse your model.

One approach we have seen is toget rid of all the “bad” methods that ActiveRecord generates for you², leaving only a whitelist of “good” methods that you can control. But we prefer another way. We love the powerful API that ActiveRecord exposes. We like to be able to use setters,create!or update_attributes!as we see fit.

So instead of fighting the nature of ActiveRecord, consider an alternative implementation of the

Inviteclass:

1 class Invite < ActiveRecord::Base 2

3 belongs_to :user

4

5 validates_presence_of :user_id, :if => :accepted?

6 7 after_save :create_membership_on_accept 8 9 private 10 11 def create_membership_on_accept

12 if accepted? && accepted_changed? 13 Membership.create!(:user => user)

14 end 15 end 16 17 end ¹http://objectsonrails.com/ ²https://github.com/objects-on-rails/fig-leaf

(19)

The implementation above works by expressing its API in validations and callbacks. This way clients of the model are free to use any ActiveRecord method to manipulate its state. It can ensure data integrity because ActiveRecord guarantees that callbacks will be called³.

Never rely on other code to use the custom model methods that you provide. To enforce an API in ActiveRecord, you must express it in validations and callbacks.

Given this statement, you might wonder if there is even a place for model methods that are not callbacks (e.g. theaccept!method from a previous code example). And there is! Feel free to pepper

your model with convenience methods that facilitate the reading and changing model records. Just do not rely on other code using those methods. Always enforce the integrity of your data with validations and callbacks.

..

Aren’t callbacks evil?

If you have been working with ActiveRecord for a while, you have probably been bitten by models with too many callbacks.

If you are now shaking your fist at our suggestion to use more instead of less callbacks, we ask for your patience. We will discuss and solve this issue in a later chapter (Dealing with Fat Models).

The true API of ActiveRecord models

Once you start expressing your model’s behavior in validations and callbacks, you realize that the API of your models actually looks like this:

• You instantiate a record using any API that suits your needs. • You manipulate the record using any API that suits your needs.

• Manipulating a record does not automatically commit changes to the database. Instead the record is put into a “dirty” state. You can inspect dirty records for errors and preview the changes that will be committed.

• Once a record passes validations, all changes can be committed to the database in a single transaction.

Once you fully embrace the restriction of only using callbacks to express yourself, you can reap many benefits:

³There are a few ActiveRecord methods likeupdate_attribute(singular) that can update a record while skipping callbacks, but they are not used during a regularsavecall.

(20)

• Your views become significantly simpler by leveraging ActiveRecord’s error tracking. For instance you no longer need to write and read controller variables to indicate an error state. Invalid form fields are highlighted automatically by the Rails form helpers.

• Developers no longer need to understand a custom API for each model they encounter, making it much easier to understand how a model behaves or how a valid record can be created. • It is no longer possible to accidentally misuse a model and end up with records in an

unexpected state.

• You can use a standard, lean controller design (see chapterBeautiful controllers).

• You will find that there are many useful libraries that work with the standard ActiveRecord API. For example thestate_machine⁴gem will prevent invalid state transitions by hooking into ActiveRecord’s validation mechanism. Or thepaper_trail⁵gem can hook into ActiveRecord’s lifecycle events to track changes of your model’s data.

Note that this is not exclusive toActiveRecordmodels. We will now take a look on how it works

for non-persisted classes.

⁴https://github.com/pluginaweek/state_machine ⁵https://github.com/airblade/paper_trail

(21)

database

In the previous chapterwe have shown that ActiveRecord’s focus on error handling makes it an effective tool to implement interactions with a human user.

However, models do not necessarily have to be backed by a database table. There are many UI interactions that do not result in a database change at all. Here are some examples:

• A sign in form • A search form

• A payment form like Stripe’s where the user enters a credit card, but the credit card data is stored on another server (using an API call)

Yet it is easy to see why you would want an ActiveRecord-like model for such interactions. After all ActiveRecord gives you so so many convenient features:

• Validations and form roundtrips, where invalid fields are highlighted and show their error message.

• Attribute setters that cast strings to integers, dates, etc. (everything is a string in the params). • Language translations for model and attribute names.

• Transactional-style form submissions, where an action is only triggered once all validations pass.

In this chapter we will show you how to configure ActiveModel to give you all the convenience of ActiveRecord without a database table, and without many lines of controller code.

Writing a better sign in form

Everyone has written a sign in form. For all purposes, a sign in form behaves like a form implemented withform_forand an ActiveRecord model. Input is validated and invalid information is highlighted

(22)

Sign in form after a validation error

The key difference to ActiveRecord is that when the form is submitted, no table row is inserted. Instead, the result of this form submission is the user being signed in (usually by setting a session cookie).

To implement this form, we start by creating a new model class that will house all the logic for this interaction. We call the classSignInand give it accessors for the user’semailand password. The

class also validates if a user with the given e-mail address exists, and if the password is correct:

app/models/sign_in.rb

1 class SignIn < PlainModel 2 3 attr_accessor :email 4 attr_accessor :password 5 6 validate :validate_user_exists 7 validate :validate_password_correct 8 9 def user

10 User.find_by_email(email) if email.present? 11 end

12

13 private

14

(23)

16 if user.blank?

17 errors.add(:user_id, 'User not found')

18 end

19 end 20

21 def validate_password_correct

22 if user && !user.has_password?(password) 23 errors.add(:password, 'Incorrect password')

24 end

25 end 26

27 end

“Wait a minute!” we hear you say. “What is thisPlainModelclass that we inherit from?”PlainModel

is little more than a 20-line wrapper around ActiveModel, which is baked into Rails. If you are not familiar with ActiveModel, it is basically a low-level construction set for classes like

ActiveRecord::Base. You can find the full source code forPlainModelbelow. For the sake of this

example, simply assume thatPlainModelis a base class that makes plain old Ruby classes feel a lot

like ActiveRecord.

Below is the controller that uses theSignInclass. It has two actions: Thenewaction shows the sign

in form while thecreateaction validates the user input and sets a session cookie if the credentials

are correct.

app/controllers/sessions_controller.rb

1 class SessionsController < ApplicationController 2 3 def new 4 build_sign_in 5 end 6 7 def create 8 build_sign_in 9 if @sign_in.save

10 # remember user in cookie here

11 else 12 render 'new' 13 end 14 end 15 16 private 17

(24)

18 def build_sign_in

19 @sign_in = SignIn.new(sign_in_params) 20 end

21

22 def sign_in_params

23 sign_in_params = params[:sign_in]

24 sign_in_params.permit(:email, :password) if sign_in_params 25 end

26 27 end

Note how the controller differs in no way from a controller that works with a database-backed ActiveRecord model. The@sign_ininstance has the same lifecycle as an ActiveRecord object:

• Attributes are set from the params

• The object is asked to validate its attributes

• If the object can be “saved”, some action is performed

In fact, the SessionsController above is almost a line-by-line copy of the default controller implementationwe introduced earlier. We like this.

Finally we have the view for the login form:

app/views/sessions/new.html.erb

1 <h1>Sign in</h1> 2

3 <%= form_for(@sign_in, url: sessions_path) do |form| %> 4

5 <%= form.label :email %> 6 <%= form.text_field :email %> 7

8 <%= form.label :password %>

9 <%= form.password_field :password %> 10

11 <%= form.submit %> 12

13 <% end %>

Note how the view also works entirely like a form for an ActiveRecord model. We can useform_for

and form helpers liketext_field. Invalid fields are automatically highlighted (Rails will wrap them

in a<div class="field_with_errors">) and retain their dirty value until the form submission was

(25)

Building PlainModel

In our example above we had theSignInclass inherit fromPlainModel, our custom wrapper around

Rails’ ActiveModel. We initially used aPlainModelthat looked like this: 1 class PlainModel

2

3 include ActiveModel::Model 4 include ActiveSupport::Callbacks

5 include ActiveModel::Validations::Callbacks 6 7 define_callbacks :save 8 9 def save 10 if valid? 11 run_callbacks :save do 12 true 13 end 14 else 15 false 16 end 17 end 18 19 end

Over time we backported more and more features from ActiveRecord intoPlainModel. For instance,

we added coercion (attributes that automatically cast their values to integers, dates, etc.) and something likebelongs_to(where setting a foreign key like user_idsets an associated record like userand vice versa).

In the end we wrapped it all in a gem calledActiveType¹. You can browse through thedocumentation on GitHub².

Using ActiveType ourSignInmodel would look like this:

¹http://rubygems.org/gems/active_type ²https://github.com/makandra/active_type

(26)

1 class SignIn < ActiveType::Object 2

3 attribute :email, :string

4 attribute :password, :string

5

6 validate :validate_user_exists

7 validate :validate_password_correct

8

9 def user

10 User.find_by_email(email) if email.present? 11 end 12 13 private 14 15 def validate_user_exists 16 if user.blank?

17 errors.add(:user_id, 'User not found')

18 end

19 end 20

21 def validate_password_correct

22 if user && !user.has_password?(password) 23 errors.add(:password, 'Incorrect password')

24 end

25 end 26

27 end

The only difference to the previous example is that we now inherit fromActiveType::Object, and

we define attributes using theattributemacro (and set a type like :string,:integer or:date)

instead of using Ruby’s attr_accessor. We will learn more about ActiveType in a later chapter,

when we talk aboutextracting interaction-specific code.

Refactoring controllers from hell

ActiveModel-based model classes are a fantastic way to refactor controllers from hell. When we say “controllers from hell” we usually mean controllers that are long, emulate validations, and contain a lot of logic.

We will now show you an example for such a controller, and how we can improve it using ActiveType. Note that this is actual production code. It supports a UI interaction that lets the user select two accounts for merging:

(27)

User selects two accounts for merging

Once the user has selected two valid accounts, the merge is performed. The merge involves the following actions:

1. All credits from the first account are transferred to the second account. 2. All subscriptions from the first account are re-assigned to the second account. 3. The first account is deleted.

4. The owner of the first account is notified by e-mail that her account no longer exists. Before refactoring, our controller from hell looked like this:

1 class AccountsController < ApplicationController 2 3 def merge_form 4 @source_missing = false 5 @target_missing = false 6 end 7 8 def do_merge

9 source_id = params[:source_id] 10 target_id = params[:target_id] 11 if source_id.blank?

12 @source_missing = true

13 end

14 if target_id.blank? 15 @target_missing = true

(28)

16 end 17

18 if source_id.present? && target_id.present? 19 source = Account.find(source_id)

20 target = Account.find(target_id) 21 Account.transaction do

22 total_credits = target.credits + source.credits 23 target.update_attributes!(:credits => total_credits) 24 source.subscriptions.each do |subscription|

25 subscription.update_attributes!(:account => target)

26 end

27 Mailer.account_merge_notification(source, target).deliver 28 source.destroy 29 end 30 redirect_to target 31 else 32 render 'merge_form' 33 end 34 35 end 36 37 end

Below you can see the view from hell that goes with that controller. Since the author did not use an ActiveRecord-like object she could not useform_forand had to do everything manually. Note how

much pain the view goes through in order to support a “form roundtrip” and display errors near invalid fields:

1 <h1>Merge two accounts</h1> 2

3 <%= form_tag do_merge_accounts_path do %> 4

5 <%= label_tag :source_id, 'Source account' %> 6

7 <% if @source_missing %> 8 <div class="error">

9 You must select a source account! 10 </div>

11 <% end %> 12

13 <% selectable_accounts = Account.all.collect { |a| [a.name, a.id] } %> 14 <%= select_tag :source_id, options_for_select(selectable_accounts) %>

(29)

15

16 <%= label_tag :target_id, 'Target account' %> 17

18 <% if @target_missing %> 19 <div class="error">

20 You must select a target account! 21 </div>

22 <% end %> 23

24 <%= select_tag :target_id, options_for_select(selectable_accounts) %> 25

26 <%= submit_tag %> 27

28 <% end %>

By moving code from the controller into a class inheriting from ActiveType we can reduce the view to the following:

1 <h1>Merge two accounts</h1> 2

3 <%= form_for @merge do |form| %> 4

5 <%= form.error_message_on :source_id %> 6 <%= form.label :source_id %>

7 <%= form.collection_select :source_id, Account.all, :id, :name %> 8

9 <%= form.error_message_on :target_id %> 10 <%= form.label :target_id %>

11 <%= form.collection_select :target_id, Account.all, :id, :name %> 12

13 <%= form.submit %> 14

15 <% end %>

Note how short and smooth the view has become! Since the@mergeobject supports the ActiveModel

API (we will see its implementation in a second) we can now useform_forand its many convenient

helpers. In case of a validation error, invalid fields are highlighted automatically and all input fields retain their dirty values.

(30)

1 class AccountMergesController < ApplicationController 2 3 def new 4 build_merge 5 end 6 7 def create 8 build_merge 9 if @merge.save

10 redirect_to @merge.target

11 else 12 render 'new' 13 end 14 end 15 16 private 17 18 def build_merge

19 @merge ||= AccountMerge.new(params[:merge]) 20 end

21 22 end

Note how the controller has almost become a line-by-line copy of thedefault controller implemen-tationwe introduced earlier.

All the crufty code to emulate validations and perform the actual merge has moved into a much better home. Meet our shiny newAccountMergeclass:

1 class AccountMerge < ActiveType::Object 2

3 attribute :source_id, :integer

4 attribute :target_id, :integer

5

6 validates_presence_of :source_id, :target_id

7

8 belongs_to :source, class_name: 'Account' 9 belongs_to :target, class_name: 'Account' 10

11 after_save :transfer_credits

12 after_save :transfer_subscriptions

13 after_save :destroy_source

(31)

15

16 private

17

18 def transfer_credits

19 total_credits = target.credits + source.credits 20 target.update_attributes!(:credits => total_credits) 21 end

22

23 def transfer_subscriptions

24 source.subscriptions.each do |subscription|

25 subscription.update_attributes!(:account => target)

26 end 27 end 28 29 def destroy_source 30 source.destroy 31 end 32 33 def send_notification

34 Mailer.account_merge_notification(source, target).deliver 35 end

36 37 end

Note how the merge has moved into private methods that are called after successful validation: 1 after_save :transfer_credits

2 after_save :transfer_subscriptions

3 after_save :destroy_source

4 after_save :send_notification

You might wonder if all we did was move code around, and add another file. And you are right! We did not remove any logic. Note how theAccountMergeclass describes its validations and merge

process much clearer. Compare it with the controller from hell we started with, where everything was just a long blob of spaghetti code.

Also, by moving the code from the controller into the model, it now lives in a place where we can unit-test it much easier than in a controller. Testing controller code requires a complex environment (request, params, sessions, etc.) whereas testing a model method simply involves calling the method and observing its behavior.

In addition, by being able to leverageform_forand its many helpers, the view has become much

shorter and easier to read.

(32)

1. Instantiate an object

2. Assign attributes from the params 3. Try tosavethe object

4. Render a view or redirect

This consistency makes it easy for another developer to understand and navigate your code. Since she already knows how your controller works, she can concentrate on theAccountMergemodel.

(33)
(34)

In our chapter “Beautiful controllers” we moved code from the controller into our models. We have seen numerous advantages by doing so: Controllers have become simpler, testing logic has become easier. But after some time, your models have started to exhibit problems of their own:

• You are afraid to save a record because it might trigger undesired callbacks (like sending an e-mail).

• Too many validations and callbacks make it harder to create sample data for a unit test. • Different UI screens require different support code from your model. For instance a welcome

e-mail should be sent when aUseris created from public registration form, but not when an

administrator creates aUserin her backend interface.

All of these problems of so-called “fat models” can be mitigated. But before we look at solutions, let’s understand why models grow fat in the first place.

Why models grow fat

The main reason why models increase in size is that they must serve more and more purposes over time. For example aUsermodel in a mature application needs to support a lot of use cases:

• A new user signs up through the registration form • An existing user signs in with her email and password • A user wants to reset her lost password

• A user logs in via Facebook (OAuth) • A users edits her profile

• An admin edits a user from the backend interface • Other models need to work withUserrecords

• Background tasks need to batch-process users every night

• A developer retrieves and changesUserrecords on the Rails console

Each of these many use cases leaves scar tissue in your model which affects all use cases. You end up with a model that is very hard to use without undesired side effects getting in your way.

Lets look at a typicalUsermodel one year after you started working on your application, and which

(35)

Use case Scar tissue inUsermodel

New user registration form Validation for password strength policy

Accessor and validation for password repetition Accessor and validation for acceptance of terms Accessor and callback to create newsletter subscription Callback to send activation e-mail

Callback to set default attributes

Protection for sensitive attributes (e.g. admin flag) Code to encrypt user passwords

Login form Method to look up a user by either e-mail or screen name Method to compare given password with encrypted password Password recovery Code to generate a recovery token

Code to validate a given recovery token Callback to send recovery link

Facebook login Code to authenticate a user from OAuth Code to create a user from OAuth

Disable password requirement when authenticated via OAuth Users edits her profile Validation for password strength policy

Accessor and callback to enable password change Callback to resend e-mail activation

Validations for social media handles

Protection for sensitive attributes (e.g. admin flag) Admin edits a user Methods and callbacks for authorization (access control)

Attribute to disable a user account Attribute to set an admin flag Other models that works with users Default attribute values

Validations to enforce data integrity Associations

Callbacks to clean up associations when destroyed Scopes to retrieve commonly used lists of users Background tasks processes users Scopes to retrieve records in need of processing

Methods to perform required task

That’s a lot of code to dig through! And even if readability wasn’t an issue, a model like this is a pain to use:

• You are suddenly afraid to update a record because who knows what callbacks might trigger. For instance a background job that synchronizes data accidentally sends a thousand e-mails because some after_savecallback informs the user that her profile was updated (“it made sense for the user profile”).

(36)

• Other code that wants to create a User finds itself unable to save the record because some

access control callback forbids it (“it made sense for the admin area”).

• Different kind of Userforms require different kind of validations, and validations from that

other form are always in the way. You begin riddling the model with configuration flags to enable or disable this or that behavior.

• Unit tests become impossible because every interaction withUser has countless side effects

that need to be muted through verbose stubbing.

The case of the missing classes

Fat models are often the symptoms of undiscovered classes trying to get out. When we take a close look at the hugetable abovewe can discover new concepts that never made it into their own classes:

• PasswordRecovery

• AdminUserForm

• RegistrationForm

• ProfileForm

• FacebookConnect

Remember when we told you that large applications are large? When you need to implement password recovery, and do not have a clear, single place to put the logic, it will still find its way into your code. It will spread itself across existing classes, usually making those classes harder to read and use.

Compare this to your apartment at home and what you do with letters you need to deal with later. Maybe there’s a stack of these letters sitting next to your keys or on your desk or dining table, probably all of the above. Because there’s no designated place for incoming letters, they are spread all over the apartment. It’s hard to find the letter you’re looking for. They clutter up your desk. And they’re in the way for dinner, too!

Of course there’s a simple solution to our letter problem. We can make a box, label it “Inbox” and put it on a shelf above our desk. With all of our letters sitting in a designated place they are no longer in the way for dinner or for working on our desk.

Code never goes away. You need to actively channel it into a place of your choice or it will infest an existing class.

Note that our apartment still contains the same number of letters as it did before. Neither can we make those letters go away. But instead of accepting an increase in clutter we have provided the organizational structure that can carry more items. Remember our chart from the first chapter?

(37)

Vanilla Rails vs. Structured Rails

Getting into a habit of organizing

Organizing your letters in an inbox is not hard. But realizing that all those letters lying around are actually yelling “Make an inbox!” takes some practice.

In similar fashion, when you are looking for a place to add new code, don’t immediately look for an existing ActiveRecord class. Instead look for new classes to contain that new logic.

In the following chapters we will show you:

• How to get into a habit of identifying undiscovered concepts in your code

• How to keep a slim core model by channelling interaction-specific support code into their own classes

• How to identify code that does not need to live inside an ActiveRecord model and extract it into service classes

(38)

code

Most mature Rails applications suffer from large models that must serve too many purposes. In our previous chapter we looked ata typicalUsermodel, and why it grew fat.

The first step to deal with a fat model should be to reduce its giant class to a slim core model. All the code you chop away goes into multiple interaction-specific form models.

Your core model should only contain the absolute minimum to exist: • A minimum set of validations to enforce data integrity

• Definitions for associations (belongs_to,has_many)

• Universally useful convenience methods to find or manipulate records

Your core model should not contain logic that is specific to individual screens and forms, such as: • Validations that only happen on a particular form (e.g. only the sign up form requires

confirmation of the user password)

• Virtual attributes to support forms that do not map 1:1 to your database tables (e.g. tags are entered into a single text field separated by comma, the model splits that string into individual tags before validation)

• Callbacks that should only fire for a particular screen or use case (e.g. only the sign up form sends a welcome e-mail)

• Code to support authorization (access control)¹

• Helper methods to facilitate rendering of complex views

All this interaction-specific logic is better placed in form models, which only exist to facilitate a single UI interaction (or a group of closely related UI interactions).

If you consistently extract interaction-specific logic into form models, your core models will remain slim and mostly free of callbacks.

(39)

A modest approach to form models

The idea of form models is nothing new. You will find that GitHub is full of gems to support “presenters”, “exhibits” or “form models”. Unfortunately you will find many of those solutions to be lacking in practice. Here are some problems we encountered:

• You can not use them with Rails’ form helpers (form_for)

• You can not use ActiveRecord macros likevalidates_presence_of

• You can not use nested forms

• They only work for individual objects, but not for a collection of objects (e.g.indexactions).

• Creating new records and editing existing records requires two different presenter classes (although the UI for both is almost identical)

• They require you to copy & paste validations and other logic from your core model • They use delegation (which can have confusing semantics ofself)

• They add a lot of files and mental overhead

Having been disappointed by various gems again and again we wondered: Is it possible to have all the convenience of ActiveRecord and still contain screen-specific logic in separate classes?

We found out that yes, we can, and we do not even need a fancy gem to do it. In fact plain vanilla inheritance is all we need in order to extract screen-specific code into their own form models. Let’s look at an example. Consider the followingUsermodel which has grown a little too fat for its

own good:

1 class User < ActiveRecord::Base 2

3 validates :email, presence: true, uniqueness: true 4 validates :password, presence: true, confirmation: true 5 validates :terms, acceptance: true

6 7 after_create :send_welcome_email 8 9 private 10 11 def send_welcome_email

12 Mailer.welcome(user).deliver 13 end

14 15 end

(40)

Clearly a lot of this code pertains to the sign up form and should be removed from theUsercore class.

You also start having trouble with this obese model, since the validations are sometimes impractical (the admin form does not require acceptance of terms) and you do not always want to send welcome e-mails whenever creating a new record, e.g. when using the console.

So let’s reduce this obese model to the minimum amount of logic that is universally useful and necessary:

1 class User < ActiveRecord::Base 2

3 validates :email, presence: true, uniqueness: true 4

5 end

All the parts that pertain to the sign up form are moved into a new classUser::AsSignUpwhich

simply inherits fromUser: 1 class User::AsSignUp < User 2

3 validates :password, presence: true, confirmation: true 4

5 validates :terms, acceptance: true 6 7 after_create :send_welcome_email 8 9 private 10 11 def send_welcome_email

12 Mailer.welcome(user).deliver 13 end

14 15 end

Note that we did not need to repeat the validation of theemailattribute within our form model.

Since the form model inherits from the core model, it automatically has all the behavior from the core model and can now build on top of that.

The controller that processes the public sign up form simply uses the new form modelUser::AsSignUp

(41)

1 class UsersController < ApplicationController 2 3 def new 4 build_user 5 end 6 7 def create 8 build_user 9 if @user.save 10 redirect_to dashboard_path 11 else 12 render 'new' 13 end 14 end 15 16 private 17 18 def build_user

19 @user ||= User::AsSignUp.new(user_params) 20 end

21

22 def user_params

23 user_params = params[:user] 24 user_params.permit( 25 :email, 26 :password, 27 :password_confirmation, 28 :terms 29 ) 30 end 31 32 end

Note that the name of the class (User::AsSignUp) is simply a naming convention we like to use.

There is nothing that forces you to prefix form models with “As” or even use a namespace. However,

we really like to organize form models in the directory structure like this:

File Class definition

user.rb class User < ActiveRecord::Base user/as_sign_up.rb class User::AsSignUp < User user/as_profile.rb class User::AsProfile < User user/as_admin_form.rb class User::AsAdminForm < User user/as_facebook_login.rb class User::AsFacebookLogin < User

(42)

See oursection on namespacingfor more about code organization.

More convenience for form models

As we kept using form models more and more, we discovered that we could make working with them more convenient by adding a few minor tweaks. Eventually we packaged these tweaks into ActiveType that we learned aboutearlier. Even though you do not need to use ActiveType to take advantage of the form model approach, here are a few examples how ActiveType can make your life easier:

• Rails helpers likeurl_for,form_for, andlink_touse the class name to guess which route to

link to. This guess is usually wrong for class names likeUser::AsSignUp. ActiveType contains

a tweak so thatlink_to(@user)calls the routeuser_path(@user)even if@useris an instance

of a form model likeUser::AsSignUp.

• When using single table inheritance² you probably do not want to store a class name like

User::AsSignUpin yourtypecolumn. This is also managed by ActiveType, soUser will be

stored as thetypeof aUser.

• In form models you often want virtual attributes with coercion (attributes that automatically cast their values to integers, dates, etc.). This is extremely useful when building forms that do not map exactly to database columns. ActiveType comes with some simple syntax to declare typed virtual attributes within the class definition of your form models. It even lets you use nested attributes³with virtual associations that don’t exist in the database.

Switching to ActiveType is simple. Instead of inheriting directly like this … 1 class User::AsSignUp < User

… we inherit like this:

1 class User::AsSignUp < ActiveType::Record[User]

Everything else will work the same as before, but you get the added convenience tweaks that we mentioned above.

Note that aside from making form models more convenient, ActiveType is also awesome to make any plain Ruby class implement the ActiveModel API. We discussed this ina previous chapter. In the next chapter we will discuss how to reduce your core model’s weight even more.

²http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance ³http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

(43)

..

Can’t I just split my model into modules?

A different technique to deal with fat models is to split them intomultiple modules, “concerns”, or “traits”.

While this technique can be useful to share behavior across multiple models, you must understand that it is only a form of file organization. Since all those modules are loaded into your model at runtime, it does not actually reduce the amount of code in your model. In different words: Callbacks and methods in your model will not go away by dividing it into multiple modules.

(44)

It is important to realize that writing ActiveModel classes involves a trade-off. ActiveModel shines when used for user-facing forms, where form roundtrips and validation errors are a core part of the user interaction. In these cases, you get a lot of convenience with a few lines of code.

But not every class inapp/modelsis required to use ActiveRecord or ActiveModel. When you don’t need the features of ActiveModel (such as validations, callbacks, dirty tracking, etc.), you should not object your class to the constraints that the ActiveModel API forces upon you. The class no longer needs to be a “bag of attributes with some callbacks”.

When looking through a fat model, you will usually find a lot of code that does not need to be tied to ActiveRecord. This code is often only called by other models, never talks to the user at all, or only supports a very simple interaction like registering an event or looking up a value.

Such code should be extracted into plain Ruby classes that do one job and do them well. A fancy name for that sort of class is “service object”, although it’s a simple Ruby class. A great way to slim down fat models is to walk through the code and look for a set of methods that can be extracted into their own service object.

Example

Consider a model called Note. The model has a search method that greps all available notes by

performing a naive MySQL LIKE query¹. So when we call Note.search('growing rails') the

resulting SQL query would look like this: 1 SELECT * FROM notes WHERE

2 title LIKE "%growing%" OR body LIKE "%growing%" OR 3 title LIKE "%rails%" OR body LIKE "%rails%";

Here is the implementation of Notethat does just that:

(45)

1 class Note < ActiveRecord::Base 2

3 validates_presence_of :title, :body

4

5 def self.search(query) 6 parts = []

7 bindings = []

8 split_query(query).each do |word|

9 escaped_word = escape_for_like_query(word) 10 parts << 'body LIKE ? OR title LIKE ?' 11 bindings << escaped_word

12 bindings << escaped_word

13 end

14 where(parts.join(' OR '), *bindings) 15 end

16

17 private

18

19 def self.split_query(query) 20 query.split(/\s+/)

21 end 22

23 def self.escape_for_like_query(phrase) 24 phrase.gsub("%", "\\%").gsub("_", "\\_") 25 end

26 27 end

When you think about it, the methodssearch,split_query, andescape_for_like_querydo not

need to live inside the model class. A trivial refactoring is to take these closely related methods and move them into a new class that is all about search:

1 class Note::Search 2

3 def initialize(query) 4 @query = query 5 end 6 7 def matches 8 parts = [] 9 bindings = []

(46)

11 escaped_word = escape_for_like_query(word) 12 parts << 'body LIKE ? OR title LIKE ?' 13 bindings << escaped_word

14 bindings << escaped_word

15 end

16 Note.where(parts.join(' OR '), *bindings) 17 end 18 19 private 20 21 def split_query 22 @query.split(/\s+/) 23 end 24

25 def escape_for_like_query(phrase)

26 phrase.gsub("%", "\\%").gsub("_", "\\_") 27 end

28 29 end

Note::Searchdoes only one thing and does it well: It takes a query and returns a set of matching

notes. It’s a simple class with a single responsibility. TheNoteclass itself is now reduced to this:

1 class Note < ActiveRecord::Base 2

3 validates_presence_of :title, :body

4 5 end

Not only have we freed Note from some code, we also created a place where new search-related requirements can live in the future. If new requirements appear, such as stemming or moving the search engine from MySQL to Lucene,Note::Searchwill be a good home for the code required to

do that.

Finding the seams along which you can cut your code into independent concepts like “Note” or “Search” or “Excel export” is one of the key strategies to keep a growing application maintainable in the long run.

Aggressively look for opportunities to extract service objects. Often times it is not hard to identify code that can be extracted without much effort. Here are more examples to get your imagination started:

(47)

Fat model style Service object style

Note.dump_to_excel(path) Note::ExcelExport.save_to(path)

User.authenticate(username, password) Login.authenticate(username, password) Project#changes Project::ChangeReport.new(project).changes Invoice#to_pdf Invoice::PdfRenderer.render(invoice)

Did we just move code around?

When looking at our Note class after the refactoring, you might notice that we did not actually

remove a single line of code. In fact we even gained a line or two by an additional class declaration. However, what used to be a monolithic blob of intertwined logic is now separated into multiple, loosely coupled components.

By stripping down our core models to basic data integrity, associations and universally useful convenience methods, our classes become easier to use:

• The side effect of any interactions are limited. For instance you are no longer afraid to save a record because it might trigger a cascade of e-mails or other updates.

• With less validations and callbacks, it is much easier to create sample records, making your core model easier to test.

• It becomes easier to navigate through your growing amount of code. Instead of having one huge class that mixes many different responsibilities, we have many small classes dedicated to a single purpose (Note::Search,Note::Export, etc.).

Just like theletter box on our deskhelps our appartment to remain tidy while carrying more items, channeling logic into interaction models and service objects creates the organizational structure that enables your application to carry more logic.

(48)

namespaces

As a Rails application grows, so does its app/models folder. We’ve seen applications grow to

hundreds of models. With an app/models directory that big, it becomes increasingly hard to

navigate. Also it becomes near-impossible to understand what the application is about by looking at themodelsfolder, where the most important models of your core domain sit next to some support

class of low significance.

A good way to not drown in a sea of.rbfiles is to aggressively namespace models into sub-folders.

This doesn’t actually reduce the number of files of course, but makes it much easier to browse through your model and highlights the important parts.

Namespacing a model is easy. Let’s say we have anInvoiceclass and each invoice can have multiple

invoice items:

1 class Invoice < ActiveRecord::Base 2 has_many :items

3 end 4

5 class Item < ActiveRecord::Base 6 belongs_to :invoice

7 end

ClearlyInvoiceis a composition of Items and anItemcannot live without a containing Invoice.

Other classes will probably interact withInvoice and not with Item. So let’s get Item out of the

way by nesting it into theInvoicenamespace. This involves renaming the class toInvoice::Item

and moving the source file toapp/models/invoice/item.rb:

app/models/invoice/item.rb

1 class Invoice::Item < ActiveRecord::Base 2 belongs_to :invoice

3 end

What might seem like a trivial refactoring has great effects a few weeks down the road. It is a nasty habit of Rails teams to avoid creating many classes, as if adding another file was an expensive thing to do. And in fact making a hugemodelsfolder even larger is something that does not feel right.

But since the models/invoice folder already existed, your team felt encouraged to create other

(49)

File Class

app/models/invoice.rb Invoice app/models/invoice/item.rb Invoice::Item app/models/invoice/reminder.rb Invoice::Reminder app/models/invoice/export.rb Invoice::Export

Note how the namespacing strategy encourages the use ofService Objectsin lieu of fat models that contain more functionality than they should.

Real-world example

In order to visualize the effect that heavy namespacing has on a real-world-project, we refactored one of our oldest applications, which was created in a time when we didn’t use namespacing. Here is themodelsfolder before refactoring:

app/models before refactoring

1 activity.rb 2 amortization_cost.rb 3 api_exchange.rb 4 api_schema.rb 5 budget_calculator.rb 6 budget_rate_budget.rb 7 budget.rb 8 budget_template_group.rb 9 budget_template.rb 10 business_plan_item.rb 11 business_plan.rb 12 company.rb 13 contact.rb 14 event.rb 15 fixed_cost.rb 16 friction_report.rb 17 internal_working_cost.rb 18 invoice_approval_mailer.rb 19 invoice_approval.rb 20 invoice_item.rb 21 invoice.rb 22 invoice_settings.rb 23 invoice_template.rb 24 invoice_template_period.rb 25 listed_activity_coworkers_summary.rb

(50)

26 note.rb 27 person.rb 28 planner_view.rb 29 profit_report_settings.rb 30 project_filter.rb 31 project_link.rb 32 project_profit_report.rb 33 project_rate.rb 34 project.rb 35 project_summary.rb 36 project_team_member.rb 37 project_type.rb 38 rate_group.rb 39 rate.rb 40 revenue_report.rb 41 review_project.rb 42 review.rb 43 staff_cost.rb 44 stopwatch.rb 45 task.rb 46 team_member.rb 47 third_party_cost.rb 48 third_party_cost_report.rb 49 topix.rb 50 user.rb 51 variable_cost.rb 52 various_earning.rb 53 workload_report.rb

Looking at the huge list of files, could you tell what the application is about? Probably not (it’s a project management and invoicing tool).

(51)

app/models after refactoring 1 /activity 2 /api 3 /contact 4 /invoice 5 /planner 6 /report 7 /project 8 activity.rb 9 contact.rb 10 planner.rb 11 invoice.rb 12 project.rb 13 user.rb

Note how the app/models folder now gives you an overview of the core domain at one glance.

Every single file is still there, but neatly organized into a clear directory structure. If we asked a new developer to change the way invoices work, she would probably find her way through the code more easily.

Use the same structure everywhere

In a typical Rails application there are many places that are (most of the time) structured like the

modelsfolder. For instance, you often see helper modules or unit tests named after your models.

When you start using namespaces, make sure that namespacing is also adopted in all the other places that are organized by model. This way you get the benefit of better organization and discoverability in all parts of your application.

Let’s say we have a namespaced model Project::Report. We should now namespace helpers,

controllers and views in the same fashion:

File Class

app/models/project/report.rb Project::Report app/helpers/project/report_helper.rb Project::ReportHelper app/controllers/projects/reports_controller.rb Projects::ReportsController app/views/projects/reports/show.html.erb View template

Note how we put the controller into aProjects(plural) namespace. While this might feel strange

References

Related documents

Our system is designed to eliminate long lines, reduce on-site staff and enhance the attendee and exhibitor experience. ExpoTRAQ’s Data Analytics and Reporting module connects

If you do not wish to use the EasyMed simplified application process, or cannot because your case does not qualify, then obtain the current state-specific life insurance

If the policy is not revived, you or your nominee, as the case may be, will be entitled to receive an amount not less than the Fund Value including Top-up Fund Value, if any,

You are invited to be in a research study of a project that will develop a hands-on, practical guide to implement a caring, HIV/AIDS ministry for local churches of the Carolina

For surface roughness, as the peak current and pulse on time are increased, the discharge energy increases and this increase in discharge energy produce a larger crater, causing

MyKochfertiliser.com.au is a simple and secure way to do business online – making it easy for you to do such things.. as manage your contracts, track your orders and keep on top of

The research also shows that when people are required to choose the total cost of the loan - rather than the monthly repayment amount - the repayment period they request is an

After all outstanding items have cleared your account and you’ve moved any direct deposits, automatic payments or withdrawals and online bill payments you’re ready to begin