Growing Rails
Growing Rails
Growing Rails
Structure large Ruby on Rails apps with the tools you already
know and love
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is 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.
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
How we got here . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
The myth of the infinitely scalable architecture . . . . . . . . . . . . . . . . . . . . . . . 1
How we structured this book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
3. Relearning ActiveRecord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Understanding the ActiveRecord lifecycle . . . . . . . . . . . . . . . . . . . . . . . . . . 13
The true API of ActiveRecord models . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Did we just move code around? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
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
1
Introduction 2
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.
• Beautiful controllers
• Relearning ActiveRecord
• User interactions without a database
Introduction 3
• On following fashions
• Surviving the upgrade pace of Rails
• Owning your stack
• The value of tests
New rules for Rails
4
2. Beautiful controllers
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.
5
Beautiful controllers 6
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.
• 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
Beautiful controllers 7
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 scaffold script 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:
def index
load_notes
end
def show
load_note
end
def new
build_note
end
def create
build_note
save_note or render 'new'
end
def edit
load_note
build_note
end
def update
load_note
build_note
save_note or render 'edit'
end
def destroy
load_note
@note.destroy
redirect_to notes_path
end
Beautiful controllers 8
private
def load_notes
@notes ||= note_scope.to_a
end
def load_note
@note ||= note_scope.find(params[:id])
end
def build_note
@note ||= note_scope.build
@note.attributes = note_params
end
def save_note
if @note.save
redirect_to @note
end
end
def note_params
note_params = params[:note]
note_params ? note_params.permit(:title, :text, :published) : {}
end
def note_scope
Note.all
end
end
• The controller actions are delegating most of their work to helper methods like load_note or
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 a search action, not visible in the example).
• There is a private method note_scope which is used by all member actions (show, edit, update,
and destroy) to load a Note with a given ID. It is also used by index to load the list of all notes.
Beautiful controllers 9
Note how at no point does an action talk to the Note model directly. By having note_scope
guard access to the Note model, we have a central place to control which records this controller
can show, list or change. This is a great technique to implement authorization schemes³
where access often depends on the current user. E.g. if we only wanted users to read and
change their own notes, we could simply have note_scope return Note.where(author_id:
current_user.id). No other changes required.
• There is a private method note_params that returns the attributes that can be set through the
update and create actions. Note how that method uses strong 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 the strong_parameters gem⁵ instead. And if you
are on Rails 2 LTS⁶ you can use Hash#slice⁷ to a similar effect. In any case we recommend
such an approach in lieu of the attr_accessible pattern 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 with nested forms⁸ or form models (which we will learn
about in a 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 techniques for providing a mapping between a user interface and your core domain
models.
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.
When Ruby loads the UsersController class, 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:
create.after do
Mailer.welcome(@user).deliver
end
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_controller and friends:
Beautiful controllers 11
• 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.
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.
12
Relearning ActiveRecord 13
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.
belongs_to :user
def accept!(user)
self.user = user
self.accepted = true
Membership.create!(user: user)
save!
end
end
The problem with the code above is that there are a dozen ways to circumvent the accept! method
by setting the user or accepted attribute with one of ActiveRecord’s many auto-generated methods.
For instance, you cannot prevent other code from simply changing the accepted flag without going
through accept!:
invite = Invite.find(...)
invite.accepted = true
invite.save!
That is unfortunate, since now we have an Invite record 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.:
• invite.update_attributes!(accepted: true)
Relearning ActiveRecord 14
In Objects on Rails¹ Avdi Grim called this the “infinite protocol” of ActiveRecord.
So what does this mean for our Invite class? 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.
One approach we have seen is to get 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.
belongs_to :user
after_save :create_membership_on_accept
private
def create_membership_on_accept
if accepted? && accepted_changed?
Membership.create!(user: user)
end
end
end
¹http://objectsonrails.com/
²https://github.com/objects-on-rails/fig-leaf
Relearning ActiveRecord 15
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. the accept! 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.
• 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 like update_attribute (singular) that can update a record while skipping callbacks, but they are not
used during a regular save call.
Relearning ActiveRecord 16
• 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 chapter Beautiful controllers).
• You will find that there are many useful libraries that work with the standard ActiveRecord
API. For example the state_machine⁴ gem will prevent invalid state transitions by hooking into
ActiveRecord’s validation mechanism. Or the paper_trail⁵ gem can hook into ActiveRecord’s
lifecycle events to track changes of your model’s data.
Note that this is not exclusive to ActiveRecord models. 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
4. User interactions without a database
In the previous chapter we 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.
17
User interactions without a database 18
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 class SignIn and give it accessors for the user’s email and 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
attr_accessor :email
attr_accessor :password
validate :validate_user_exists
validate :validate_password_correct
def user
User.find_by_email(email) if email.present?
end
private
def validate_user_exists
User interactions without a database 19
if user.blank?
errors.add(:user_id, 'User not found')
end
end
def validate_password_correct
if user && !user.has_password?(password)
errors.add(:password, 'Incorrect password')
end
end
end
“Wait a minute!” we hear you say. “What is this PlainModel class 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 for PlainModel below. For the sake of this
example, simply assume that PlainModel is a base class that makes plain old Ruby classes feel a lot
like ActiveRecord.
Below is the controller that uses the SignIn class. It has two actions: The new action shows the sign
in form while the create action validates the user input and sets a session cookie if the credentials
are correct.
app/controllers/sessions_controller.rb
def new
build_sign_in
end
def create
build_sign_in
if @sign_in.save
# remember user in cookie here
else
render 'new'
end
end
private
User interactions without a database 20
def build_sign_in
@sign_in = SignIn.new(sign_in_params)
end
def sign_in_params
sign_in_params = params[:sign_in]
sign_in_params.permit(:email, :password) if sign_in_params
end
end
Note how the controller differs in no way from a controller that works with a database-backed
ActiveRecord model. The @sign_in instance has the same lifecycle as an ActiveRecord object:
In fact, the SessionsController above is almost a line-by-line copy of the default controller
implementation we introduced earlier. We like this.
Finally we have the view for the login form:
app/views/sessions/new.html.erb
<h1>Sign in</h1>
Note how the view also works entirely like a form for an ActiveRecord model. We can use form_for
and form helpers like text_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
successful.
User interactions without a database 21
Building PlainModel
In our example above we had the SignIn class inherit from PlainModel, our custom wrapper around
Rails’ ActiveModel. We initially used a PlainModel that looked like this:
class PlainModel
include ActiveModel::Model
include ActiveSupport::Callbacks
include ActiveModel::Validations::Callbacks
define_callbacks :save
def save
if valid?
run_callbacks :save do
true
end
else
false
end
end
end
Over time we backported more and more features from ActiveRecord into PlainModel. For instance,
we added coercion (attributes that automatically cast their values to integers, dates, etc.) and
something like belongs_to (where setting a foreign key like user_id sets an associated record like
user and vice versa).
In the end we wrapped it all in a gem called ActiveType¹. You can browse through the documentation
on GitHub².
Using ActiveType our SignIn model would look like this:
¹http://rubygems.org/gems/active_type
²https://github.com/makandra/active_type
User interactions without a database 22
validate :validate_user_exists
validate :validate_password_correct
def user
User.find_by_email(email) if email.present?
end
private
def validate_user_exists
if user.blank?
errors.add(:user_id, 'User not found')
end
end
def validate_password_correct
if user && !user.has_password?(password)
errors.add(:password, 'Incorrect password')
end
end
end
The only difference to the previous example is that we now inherit from ActiveType::Object, and
we define attributes using the attribute macro (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 about extracting interaction-specific code.
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.
def merge_form
@source_missing = false
@target_missing = false
end
def do_merge
source_id = params[:source_id]
target_id = params[:target_id]
if source_id.blank?
@source_missing = true
end
if target_id.blank?
@target_missing = true
end
end
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 use form_for and 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:
User interactions without a database 25
By moving code from the controller into a class inheriting from ActiveType we can reduce the view
to the following:
Note how short and smooth the view has become! Since the @merge object supports the ActiveModel
API (we will see its implementation in a second) we can now use form_for and its many convenient
helpers. In case of a validation error, invalid fields are highlighted automatically and all input fields
retain their dirty values.
Here is the refactored controller that renders and processes this new form:
def new
build_merge
end
def create
build_merge
if @merge.save
redirect_to @merge.target
else
render 'new'
end
end
private
def build_merge
@merge ||= AccountMerge.new(params[:merge])
end
end
Note how the controller has almost become a line-by-line copy of the default controller implemen-
tation we introduced earlier.
All the crufty code to emulate validations and perform the actual merge has moved into a much
better home. Meet our shiny new AccountMerge class:
User interactions without a database 27
after_save :transfer_credits
after_save :transfer_subscriptions
after_save :destroy_source
after_save :send_notification
private
def transfer_credits
total_credits = target.credits + source.credits
target.update_attributes!(credits: total_credits)
end
def transfer_subscriptions
source.subscriptions.each do |subscription|
subscription.update_attributes!(account: target)
end
end
def destroy_source
source.destroy
end
def send_notification
Mailer.account_merge_notification(source, target).deliver
end
end
Note how the merge has moved into private methods that are called after successful validation:
User interactions without a database 28
after_save :transfer_credits
after_save :transfer_subscriptions
after_save :destroy_source
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 the AccountMerge class 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 leverage form_for and its many helpers, the view has become much
shorter and easier to read.
And finally the controller now uses the same control flow as any other controller in your application:
1. Instantiate an object
2. Assign attributes from the params
3. Try to save the 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 the AccountMerge model.
Creating a system for growth
29
5. Dealing with fat models
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 a User is created from public registration form, but not when an
administrator creates a User in 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.
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 typical User model one year after you started working on your application, and which
use cases have left scars in the model code:
30
Dealing with fat models 31
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_save callback informs the user that her profile was updated (“it made
sense for the user profile”).
• Other code that wants to create a User finds itself unable to save the record because some
Dealing with fat models 32
access control callback forbids it (“it made sense for the admin area”).
• Different kind of User forms 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 with User has countless side effects
that need to be muted through verbose stubbing.
• 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?
Dealing with fat models 33
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.
34
A home for interaction-specific code 35
• You can not use them with Rails’ form helpers (form_for)
• You can not use ActiveRecord macros like validates, before_validation or after_initial-
ize
• You can not use nested forms
• They only work for individual objects, but not for a collection of objects (e.g. index actions).
• 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 of self)
• 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 following User model which has grown a little too fat for its
own good:
after_create :send_welcome_email
private
def send_welcome_email
Mailer.welcome(user).deliver
end
end
Clearly a lot of this code pertains to the sign up form and should be removed from the User core 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:
A home for interaction-specific code 36
end
All the parts that pertain to the sign up form are moved into a new class User::AsSignUp which
simply inherits from User:
after_create :send_welcome_email
private
def send_welcome_email
Mailer.welcome(user).deliver
end
end
Note that we did not need to repeat the validation of the email attribute 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 model User::AsSignUp
instead of the original User class
def new
build_user
end
def create
build_user
if @user.save
redirect_to dashboard_path
A home for interaction-specific code 37
else
render 'new'
end
end
private
def build_user
@user ||= User::AsSignUp.new(user_params)
end
def user_params
user_params = params[:user]
user_params.permit(
:email,
:password,
:password_confirmation,
:terms
)
end
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:
advantage of the form model approach, here are a few examples how ActiveType can make your life
easier:
• Rails helpers like url_for, form_for, and link_to use the class name to guess which route to
link to. This guess is usually wrong for class names like User::AsSignUp. ActiveType contains
a tweak so that link_to(@user) calls the route user_path(@user) even if @user is an instance
of a form model like User::AsSignUp.
• When using single table inheritance² you probably do not want to store a class name like
User::AsSignUp in your type column. This is also managed by ActiveType, so User will be
stored as the type of a User.
• 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.
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 in a 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
A home for interaction-specific code 39
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.
http://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns
7. Extracting service objects
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 in app/models is 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:
¹http://dev.mysql.com/doc/refman/5.7/en/string-comparison-functions.html
40
Extracting service objects 41
def self.search(query)
parts = []
bindings = []
split_query(query).each do |word|
escaped_word = escape_for_like_query(word)
parts << 'body LIKE ? OR title LIKE ?'
bindings << escaped_word
bindings << escaped_word
end
where(parts.join(' OR '), *bindings)
end
private
def self.split_query(query)
query.split(/\s+/)
end
def self.escape_for_like_query(phrase)
phrase.gsub("%", "\\%").gsub("_", "\\_")
end
end
When you think about it, the methods search, split_query, and escape_for_like_query do 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:
class Note::Search
def initialize(query)
@query = query
end
def matches
parts = []
bindings = []
split_query.each do |word|
Extracting service objects 42
escaped_word = escape_for_like_query(word)
parts << 'body LIKE ? OR title LIKE ?'
bindings << escaped_word
bindings << escaped_word
end
Note.where(parts.join(' OR '), *bindings)
end
private
def split_query
@query.split(/\s+/)
end
def escape_for_like_query(phrase)
phrase.gsub("%", "\\%").gsub("_", "\\_")
end
end
Note::Search does 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.
The Note class itself is now reduced to this:
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::Search will 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:
Extracting service objects 43
• 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 the letter box on our desk helps 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.
8. Organizing large codebases with
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
the models folder, 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 .rb files 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 an Invoice class and each invoice can have multiple
invoice items:
Clearly Invoice is a composition of Items and an Item cannot live without a containing Invoice.
Other classes will probably interact with Invoice and not with Item. So let’s get Item out of the
way by nesting it into the Invoice namespace. This involves renaming the class to Invoice::Item
and moving the source file to app/models/invoice/item.rb:
app/models/invoice/item.rb
class Invoice::Item < ActiveRecord::Base
belongs_to :invoice
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 huge models folder even larger is something that does not feel right.
But since the models/invoice folder already existed, your team felt encouraged to create other
invoice-related models and place them into this new namespace:
44
Organizing large codebases with namespaces 45
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 of Service Objects in 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 the models folder before refactoring:
activity.rb
amortization_cost.rb
api_exchange.rb
api_schema.rb
budget_calculator.rb
budget_rate_budget.rb
budget.rb
budget_template_group.rb
budget_template.rb
business_plan_item.rb
business_plan.rb
company.rb
contact.rb
event.rb
fixed_cost.rb
friction_report.rb
internal_working_cost.rb
invoice_approval_mailer.rb
invoice_approval.rb
invoice_item.rb
invoice.rb
invoice_settings.rb
invoice_template.rb
invoice_template_period.rb
listed_activity_coworkers_summary.rb
Organizing large codebases with namespaces 46
note.rb
person.rb
planner_view.rb
profit_report_settings.rb
project_filter.rb
project_link.rb
project_profit_report.rb
project_rate.rb
project.rb
project_summary.rb
project_team_member.rb
project_type.rb
rate_group.rb
rate.rb
revenue_report.rb
review_project.rb
review.rb
staff_cost.rb
stopwatch.rb
task.rb
team_member.rb
third_party_cost.rb
third_party_cost_report.rb
topix.rb
user.rb
variable_cost.rb
various_earning.rb
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).
Let’s look at the refactored version:
Organizing large codebases with namespaces 47
/activity
/api
/contact
/invoice
/planner
/report
/project
activity.rb
contact.rb
planner.rb
invoice.rb
project.rb
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.
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 a Projects (plural) namespace. While this might feel strange
at first, it allows for natural nesting of folders in in app/views:
Organizing large codebases with namespaces 48
app/
views/
projects/
index.html.erb
show.html.erb
reports/
show.html.erb
If we put the controller into a Project (singular) namespace, Rails would expect view templates in
a structure like this:
app/
views/
project/
reports/
show.html.erb
projects/
index.html.erb
show.html.erb
Note how two folders project (singular) and projects (plural) sit right next to each other. This
doesn’t feel right. We feel that the file organization of our views is more important than keeping
controller namespace names in singular form.
File Description
spec/models/project/report_spec.rb Model test
spec/controllers/projects/reports_controller_spec.rb Controller test
features/project/reports.feature Cucumber untegration test
features/step_definitions/project/report_steps.rb Step definitions
If another way to split up your files feels better, just go ahead and do it. Do not feel forced to be
overly consistent, but always have a good default.
9. Taming stylesheets
You might be surprised to read about CSS in a book about maintainable Rails applications.
However, we found that there is hardly any part of your application that can spiral out of control as
quickly and horribly as a stylesheets folder.
Does the following sound familiar?
• You are afraid to change a style because it might break screens you are not aware of.
• An innocent tag like <label> or <p> immediately inherits a million styles and you spend a lot
of time overwriting inherited styles that get into your way before you can get started with
your own work.
• You often see your own styles being overshadowed by other styles that are more specific
because of rules you do not care about. You often need to go nuclear with !important.
In this chapter we want to examine why CSS grows unmaintainable, and what we can do to bring
back order and sanity.
50
Taming stylesheets 51
<div class="article">
<img src="avatar.png" />
<h1>Article title</h1>
<p>Lorem ipsum...</p>
</div>
.article img {
width: 200px;
float: right;
}
We test the stylesheet in a browser. Everything looks great and we deploy the first version of our
blog.
A few hours later an e-mail arrives. It’s a feature request from our product manager: At the end
of each article there should be links to share the article via Twitter and Facebook. The e-mail even
included a mock-up of the desired look:
Taming stylesheets 52
No problem! We quickly add a <div class="social"> container to our HTML and nest links to our
social profiles inside it:
<div class="article">
<img src="avatar.png" />
<h1>Article title</h1>
<p>Lorem ipsum...</p>
<div class="social">
<a href=".."><img src="twitter.png" /></a>
<a href=".."><img src="facebook.png" /></a>
</div>
</div>
When we test these changes in our browser, we realize that the images are huge and that they are
hanging on the right side of the screen. And oddly, the Twitter and Facebook links seem to have
switched places:
Taming stylesheets 53
We go back to our HTML and quickly see what went wrong. When we implemented the author
avatar, we applied a size and float style to all the <img> tags inside an .article. And the icon
images for Twitter and Facebook now inherit those styles.
But we won’t give up that easily. After all we can simply reset the inherited styles for <img> within
a .social container:
.social img {
width: auto;
float: none;
}
We test our changes in the browser and all is well. We deploy the changes and inform our product
manager.
New feature request! The homepage should have a list of recent articles. However, articles on the
homepage should not show author avatars because it distracts from the huge banner image on the
home page. No problem! We will just give the <body> tag on the homepage a class homepage like
this:
Taming stylesheets 54
<html>
<body class="homepage">
...
</body>
</html>
Now we can hide the author avatars on the homepage like this:
We check the browser for changes. The avatars are gone, so we go ahead and deploy.
It only takes a few minutes for the first bug report to come in. Articles on the homepage are missing
their social media links. Embarrassed we quickly deploy a fix that restores them:
While that fixes the site for now, the CSS we have created is a mess of awkward style dependencies:
This cascade of rules overshadowing one another is the “C” in CSS. Its effect is nothing like
inheritance in object-oriented programming languages. In CSS every attribute of every selector can
be overshadowed by any number of attributes from any other selector. The sum of all side effects
of all selectors determines how the site renders in the end. The problem grows with the number of
styles in your stylesheet.
No sane programmer would allow her Ruby code to work like this. As programmers we strive for
simple APIs with clear input and output. We avoid side effects and tight coupling whenever we can.
We should do the same for our stylesheets.
Luckily, there is a better way.
Taming stylesheets 55
A systematic approach to CSS that delivers all of the above is BEM (short for Block, Element,
Modifier). BEM is not a library you can download. It is a set of rules how to structure your stylesheets.
BEM is to your oldschool stylesheets what a cleanly cut domain model is to 10000 lines of PHP
spaghetti code. It divides responsibilities and provides a clear API for your styles. Most importantly,
it draws clear lines to dictate where side effects may flow.
At first, BEM will feel limiting. You might even ask yourself why you should restrict your freedom
to write styles in any way you want. But just like spaghetti vs. OOP, the benefits will quickly
become clear with use. After a week with BEM the old school way of writing CSS will feel dirty and
unprofessional to you.
Blocks
In BEM you structure every style in the same, uniform way. Instead of wildly adding styles where
it makes sense at the time, BEM stylesheets consist of a simple, flat list of “blocks”.
Here are some examples for blocks:
• A navigation bar
• A blog article
• A row of buttons
• Columns that divide the available horizontal space
Think of blocks as the classes of your stylesheet API. Blocks are implemented as simple CSS selectors:
Taming stylesheets 56
.navigation {
...
}
.article {
...
}
.buttons {
...
}
Elements
Components are often more complex than a single style rule. That’s why BEM has the notion of
“elements”.
Here are some examples for elements:
Think of elements as the methods of your stylesheet classes (blocks). Elements are also implemented
as simple CSS selectors, prefixed with the block name:
<div class="article">
<div class="article--title">
Awesome article
</div>
<div class="article--text">
Lorem ipsum dolor ...
</div>
</div>
.article {
...
}
.article--title {
...
}
.article--text {
...
}
Note that elements can only exist as the child of one specific block, made explicit by the prefixed
block name. An element can never exist without a matching block to contain it.
Also note that the list of selectors is again flat. We avoid nesting selectors to evade selector
specificity¹. This keeps the overshadowing rules very simple - styles can be overwritten by styles
further down in the file. It can also improve browser rendering performance.²
You might sniff your nose at the double underscores that separate the block name from the element
name. There are other conventions, such as double underscores (article__title). In any case you
should pick a separator that you can distinguish from a regular word boundary (e.g. the single dash
in navigation-bar). Whatever syntax you pick, you will get used to it within a day.
Modifiers
We do not like to repeat code. So what if we need a block that behaves 90% like another block?
Think of modifiers as the optional parameters of your stylesheet classes (blocks) and methods
(elements). Modifiers are implemented as conjunctive selectors on an existing block or modifier.
Modifiers then use the cascade to overshadow existing styles from the same block:
¹https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity
²Browsers need to match every DOM node against every CSS selector. Short rules can be matched faster than long chains of nested selectors.
Taming stylesheets 58
.button {
background-color: #666;
...
}
.button.is-primary {
background-color: #35d;
}
Note that modifiers are not prefixed with the name of their block. Instead we choose names that
indicate “modifierness” such as is-small or has-children. Since modifiers only attach to existing
blocks or elements, we do not need to worry about name conflicts due to the missing prefix.
Block modifiers can also modify elements as long as they are within the same block:
.article.is-summary .article--title {
font-size: 12px;
...
}
The BEM prime directive: A block must never influence the style of other blocks.
Let’s look how a violation of the BEM prime directive looks like. For this let’s assume we have an
.article block to style a blog article. We also have a .sidebar block for secondary information that
is displayed alongside our main content.
Sometimes we want to display the summary of a related article in the sidebar. Article summaries
should be displayed in a smaller font. The quickest way to write that in CSS is this:
.article {
...
}
.sidebar .article {
font-size: 12px;
}
That violates the BEM prime directive. By modifying the .article style in the context of .sidebar
we created a dependency between those two selectors. Our .article is no longer an independent
block, since changes in .sidebar can now break .article.
Thoughtlessly coupling blocks together will throw us back into the style dependency hell that we
just emerged from.
Resolving violations
If you need an .article to behave differently within a .sidebar, you have two options. You could
make .article an element of .sidebar:
.sidebar {
...
}
.sidebar--article {
font-size: 12px;
}
.article {
...
}
.article.is-summary {
font-size: 12px;
}
.sidebar {
...
}
This way .article can implement the desired effect (being smaller) without knowing about
.sidebar. Since there is no coupling between the two selectors, they can both be used and refactored
independently from each other.
Below is the HTML required to implement this layout in BEM-style. Note that we used <div> tags
for everything, even though there are more meaningful tags in HTML5. We did this to keep the
example focused on structure and class name choices.
<div class="layout">
<div class="layout--head">
<div class="logo">...</div>
<div class="navigation">
<a href="..." class="navigation--section is-current">Home</a>
<a href="..." class="navigation--section">Archives</a>
<a href="..." class="navigation--section">About</a>
</div>
</div>
<div class="layout--main">
<div class="article">
Taming stylesheets 62
</div>
<div class="layout--side">
</div>
</div>
.layout {
...
}
.layout--head {
...
}
.layout--main {
...
Taming stylesheets 63
.layout--side {
...
}
.logo {
...
}
.navigation {
...
}
.navigation--section {
...
}
.navigation--section.is-current {
...
}
.title {
...
}
.article {
...
}
.article--title {
...
}
.article--text {
...
}
.article.is-summary .article--title {
...
}
Taming stylesheets 64
.article.is-summary .article--text {
...
}
• No block ever influences another block. For instance, even though articles in the sidebar are
smaller than in the main content area, an .article doesn’t simply render smaller within
.layout--side. Instead we choose an explicit modifier article.is-small to contain the style
variant within the .article block.
• Class names are unambiguous. Although there are two kinds of “titles” in the example, they
have two distinct names: .title indicates a section heading, .article--title indicates the
title of an article. Not reusing names is important to prevent the CSS cascade and keep blocks
independent from each other.
• The page layout is simply another block .layout. The individual parts of the layout, such as
page head, main content area and sidebar, make up the elements of the .layout block.
• Sometimes it is ambiguous whether a new element should become its own block or if it should
be added as an element of an existing block. In the example above it could be argued that .logo
should be .layout--logo, since the site logo is part of the layout. Often both solutions are fine.
Do be wary of “god” blocks that grow too large.
Organizing stylesheets
We recommend to use one file for each BEM block. This enforces the independence of blocks. If you
do not even see another block selector in the same file, you are even less tempted to violate the BEM
prime directive. Foreign block selectors should stick out like a sore thumb.
Your stylesheets folder will look like this:
app/assets/stylesheets
normalize.css
blocks/
button.css
columns.css
layout.css
map.css
pagination.css
You can easily concat these many files using the Rails asset pipeline³:
³http://guides.rubyonrails.org/asset_pipeline.html
Taming stylesheets 65
app/assets/stylesheets/application.css
/*
*= require ./normalize
*= require_tree ./blocks
*/
BEM anti-patterns
Using BEM is no guarantee for a maintainable library of reusable styles. We have seen horrible BEM
stylesheets, featuring anti-patterns like the following:
It is easy to get side-tracked by all the restrictions that BEM places on you, especially when you have
years of bad CSS habits to unlearn. Struggling to implement your designs at all you might resort to
one of the anti-patterns listed above.
It’s important to never forget the point of all of this. What we are doing here is creating a public
API for your stylesheets. Put yourself in the position of your colleague developer who starts on your
project next week. What would you like to see?
Your stylesheets should be like a delicious menu of aptly named and reusable styles from which you
can compose new and beautiful screens. A BEM block should scream at you what it does.
Developers can now browse this document to see which BEM blocks are available for reuse or
extension. This greatly promotes the care of your style library. A team with a living style guide is
less likely to employ BEM anti-patterns.
The annotations can live in CSS comments above your styles like this:
/*doc
---
title: Buttons
name: button
category: Styles
---
```html_example
<button class="button">Click</button>
<a class="button is-warning" href="http://www.google.com">Don't click</a>
```
*/
.button {
...
Taming stylesheets 67
.button.is-warning {
...
}
In order to generate the style guide for the example above we used the hologram gem⁴ from the
people at Trulia⁵. There are many other gems that do similar things. What we like about hologram
is that the documentation lives in the BEM blocks themselves (rather than in a a separate file).
Pragmatic BEM
BEM is a coding discipline. In order to enjoy the advantages of independent styles and better
organisation you need to stick to the rules. Once you start breaking the BEM prime directive the
benefits will go away and you are left with a folder of strangely named styles.
However, there are instances where it makes sense to slightly deviate from the BEM dogma.
An example for this are compound HTML structures. The block/element relationship can be found in
vanilla HTML. For example a <table> tag comprises multiple <tr> and <td> elements. If we made
a table block like table.grid, BEM dogma would require us to add classes to every <tr> and <td>
it contains. The resulting HTML structure would like this:
<table class="grid">
<tr class="grid--row">
<td class="grid--cell">...</td>
<td class="grid--cell">...</td>
</tr>
</table>
We believe this is too much code for little benefit. In practice we assume an implicit block/element
relationship between <table> and the contained <tr> and <td>. We would only write this:
⁴http://trulia.github.io/hologram/
⁵http://www.trulia.com/
Taming stylesheets 68
<table class="grid">
<tr>
<td>...</td>
<td>...</td>
</tr>
</table>
When to be pragmatic
We made some rules for ourselves when to consider a pragmatic interpretation of BEM:
• We never violate the BEM prime directive. Once we assign a BEM block name, we never
reference it from another block.
• The effect of our pragmatism must be contained within small boundaries. In the example
above, styling <td> tags within a table.grid only affects table cells within that table.grid
block, so we feel the damage is contained. However, if our design or content would often
feature nested tables of different block types, we would give <tr>s and <td>s element names.
Here are some examples where we give ourselves a little freedom to violate the BEM dogma:
• We don’t give classes to the children of compound HTML elements (<table>, <dl>, <ul>, …)
• We allow CSS to insert unnamed elements with :before and :after statements instead of
adding explicit elements into the HTML.
• We allow pseudo-selectors like :hover or :first-child (instead of using an implicit modifier).
• We allow media queries (instead of using an implicit modifier set by Javascript).
• When we have long-form text content (e. g. the text of a blog post) with a limited set of tags
(like <p>, <b> and <a>) we sometimes style plain HTML tags within a container like .text-
content. This is often the case when we let users enter formatted text in a WYSIWYG or
Markdown editor, and we want to display that text on the site. It would simply be impractical
to go through the user-provided HTML and replace every <a> tag with a <a class="post--
hyperlink">.
• Sometimes other libraries generate HTML for us, e.g. for a lightbox or another canned UI
widget. We cannot always control how these libraries use CSS class names, and generated
HTML rarely fits into whatever BEM structure we decided on for our own HTML. In that
case we simply style the generated tags within a container.
Flirting with a pragmatic interpretation of BEM’s structural rules is a dangerous game. It can save
a lot of time, but make a few badly chosen exceptions and you will be back in the hell of style
dependencies that you came from.
So tread carefully. And if you are ever unsure, just be strict about it.
Building applications to last
69
10. On following fashions
The ecosystem of Ruby on Rails is not immune to fashion. From one day to another social media is
buzzing with praise for a new architectural pattern, or for a new gem that promises to completely
change the way you think about Ruby.
It is often hard to judge which of these fashions are just short-lived trends, and which are here to
stay.
• Does the code feel easier to read and change after the refactoring? Can you find words to back
up your impression?
• How did the amount of code, the number of files, the coupling between classes change? If
there is an increase of artifacts or complexity, is it outweighed by the benefits you gain?
• Did you run into new issues that the old code did not have?
Understanding trade-offs
There are no silver bullets. New patterns will always be incremental improvements, or simply
exchange one trade-off for another. One technique might have prettier syntax, but makes your
classes harder to test. Another pattern might make your code a lot shorter, but at the price of too
much magic behavior going on behind the scenes.
Get into a habit of scanning new patterns for trade-offs. See our note on controller abstractions for
a detailed breakdown of one technology and the trade-offs it involves.
70
On following fashions 71
72
Surviving the upgrade pace of Rails 73
Be careful when monkey patching the internals of a class that does not belong to you. You will pay
for it later.
If you find a bug in a gem that you like, consider forking the gem, committing your fix with a test
and creating a pull request to the original author. This way your monkey patch can soon be replaced
with the next official version of the gem. It also makes for good karma.
In order to decide whether adding a new library is worth the cost of ownership, here are some
questions to ask yourself:
• Take a glance at the source code by browsing through the GitHub repository. Does the code
look well-groomed or is it a mess of long classes and methods?
• Does it have tests? You should see a test or spec directory in the GitHub repository. Make
sure that it contains actual tests, not just auto-generated stubs.
• Is the library under active development? Check the list of recent commits and if the
maintainers are responding to GitHub issues.
• Are you ready to maintain or replace this library if the maintainers lose interest?
• Is the functionality provided by the library significant enough to accept the cost of integration
and future maintenance? Take a minute to sketch out the effort required to build the
functionality yourself. For example instead of adding a library for autocomplete suggestions
you could also add a new controller action and some lines of jQuery. But rolling your own
XML parser would mean implementing a complex format with all of its intricacies.
¹https://www.ruby-toolbox.com/
74
Owning your stack 75
By making informed decisions about when to accept a library into your stack you can speed up
development without sabotaging future maintenance work.
Note that we do not recommend to flat out refuse new technology. Our point is that you should
consider going with a simple solution until your load or data volume justifies the switch.
To be able to easily swap in a new solution later, remember our advice about service objects and hide
service access behind a simple API. Say you are implementing a full text search and decide to start
with simple MySQL LIKE queries instead of integrating an indexer like Solr. Instead of scattering
your code with MySQL queries, hide the implementation details behind a service object like this:
²Whenever is a tiny gem that automatically installs Cron jobs when you deploy with Capistrano.
Owning your stack 76
This way, when your data outgrows MySQL and you need a more sophisticated replacement, you
can simply swap out the implementation of Article::Search.find without needing to touch other
code.
13. The value of tests
Learning to test your applications effectively might be the most important skill you can learn in
your entire career. If we could offer you only one tip for the future, tests would be it.
Recognized benefits you can gain from an effective test suite include the following:
• The frequency of bugs is reduced to a point where you don’t think about bugs at all anymore.
With that comes a lot of peace of mind. No more lying awake at night, wondering if yesterday’s
code release is silently eating up customer data.
• The ability to release often. Since you don’t need to manually test every part of your
application after a change you can release whenever your test suite is green. New features
can be shipped to customers and generate business value in no time.
• The freedom to refactor without fear. Since tests will tell you if a refactoring inadvertently
breaks peripheral functionality, you can move code around with broad strokes instead of
carefully tip-toeing through your source.
• The ability to work on one part of the application without knowing the rest. This becomes
critical once your application or team grows to a point where no single person can memorize
the whole code base anymore. Having a safety net for regressions is also great for new team
members, or when you jump back and forth between multiple projects.
Many books have been written about software testing, and you will probably not find two developers
who would completely agree about what to test, or how to test it.
What we want to do in this chapter is to give you a high-level overview of what has worked for us.
77
The value of tests 78
We have found the most value in unit tests and full-stack integration tests. We use unit tests to
describe the fine-grained behavior of classes and methods. We use full-stack integration to test the
frontend from a user’s point of view, with a simulated web browser that magically clicks through
the user interface, completes forms, presses buttons and so on.
Here is an example for a full-stack integration test written in Cucumber¹. It tests a search form for
an address book with company contacts:
In addition to the integration test above, we usually have multiple unit tests like the following:
describe Company::Search do
describe '.find' do
it 'should be case-insensitive' do
match = Company.make(name: 'Acme')
no_match = Company.make(name: 'Madrigal')
Company::Search.find('aCmE').should == [match]
end
end
¹http://cukes.info/
The value of tests 79
Note how the unit test is covering a lot more edge cases (like case insensitivity) than the integration
test. We could have tested each of these edge cases with integration tests. However, this would have
required an awful lot of overhead only to run a simple query and observe the results.
On the other hand, we wouldn’t want to leave out the integration test. If we only had the unit test,
a lot of code in the controller and view would have no test coverage at all.
Here are some practical guidelines for choosing the right type of test:
1. Try to cover as much ground as possible using unit tests, which are much faster and easier to
set up than full-stack integration tests. By moving as much code as possible from controllers
and views into form models and service objects, large chunks of behavior will live in a place
where it is easy to benefit from a short and fast unit test.
2. Every UI screen should be “kissed” by at least one full-stack integration test. It is sufficient
for the test to quickly run one or two paths through the screen, without necessarily covering
every edge case. For instance, test that a form shows validation errors, but do not test every
single combination of errors.
3. Prefer unit tests to test methods with many code paths or edge cases. An example for this is
the Company::Search.find method in the example above.
In the end, it always comes down to what you care about the most. If you are an online merchant
you probably want the checkout process to be tested in great detail. The same merchant might not
care so much about the admin backend that only two people ever see.
The value of tests 80
it even exists. Then implement the class once its usage feels right. You might have heard about this
as “test-driven design”. By practicing test-driven design you can significantly improve the quality
of your classes and methods.
Even if all you have is this one test you are already so much better off than when you had to manually
click through the order process whenever you make a change to the code. Whatever you do from
here on, you can always rely on that test to know that the checkout process works flawlessly.
Now you have a great foundation to expand upon. Whenever you make a change, add another test
to describe the new behavior. For instance, when you add a feature to remove an item from the
shopping cart, add a test that checks that the item is actually gone.
instance if your tests require sample data in the database, the test cases should insert the required
records automatically.
Another principle you should put in place is that no code should be committed with failing or
pending tests. Once your test suite is anything but 100% green, it degrades quickly. It can no longer
give you feedback if a change has introduced a regression, if it was already failing before the change.
It also lowers the barrier for your team to accept more and more failing test cases, since no-one yelled
about the previous one.
14. Closing thoughts
This has been version 1.0 of Growing Rails Applications in Practice. Did you find it useful? Please
let us know by sending your feedback to info@growingrails.com.
In case you need help with your application, note that you can hire us for Rails development and
consulting. Simply send a message to info@makandra.com and we’ll be in touch.
Finally, if you enjoyed the book, please help us spread the word on Twitter and other social networks.
The best way to tell others about the book is to link to our website:
http://growingrails.com¹
¹http://growingrails.com
83
Closing thoughts 84
Copyright notices
Cover image
©iStock.com/feoris