I was running through tutorials for Elixir + Phoenix, and got to the part where forms start showing validation failures. Specifically this code, from Pragmatic Programmers' "Programming Phoenix" book:

<%= form_for @changeset, user_path(@conn, :create), fn f -> %> 
  <%= if f.errors != [] do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below:</p>
      <ul>
        <%= for {attr, message} <- f.errors do %>
          <li><%= humanize(attr) %> <%= message %></li>
        <% end %> 
      </ul>
    </div>
  <!-- snip -->
<% end %>

I got this error: no function clause matching in Phoenix.HTML.Safe.Tuple.to_iodata/1

Couldn't find a bloody solution anywhere. Took a long time to find IO.inspect . The message thing turned out to be a tuple that looked made for sprintf - something like {"name can't be longer than %{count}", count: 1} , so I spent forever trying to figure out if Elixir has sprintf , looked like there might be something in :io.format() , then I had to learn about Erlang bindings, but that wasn't...

Ended up on #elixir-lang IRC channel, and the author of the book (Chris Mccord) pointed me to the Ecto "error helpers" in this upgrade guide. It's a breaking change in Phoenix 1.1 + Ecto 2.0. The book is (and I imagine many tutorials are) for Phoenix 1.0.x , and I had installed the latest at 1.1.0 .

Major thanks to Chris - I have literally never had a question actually get answered on IRC. It was a last-resort measure, and it really says something about the Elixir community that someone helped me figure this out.

Stretchy is an ActiveRecord-esque query builder for Elasticsearch. It's not stable yet (hence the <1.0 version number), and Elasticsearch has been moving so fast it's hard to keep up. The major change in 2.0 was eliminating the separation between queries and filters, a major source of complexity for the poor gem.

For now my machine needs Elasticsearch 1.7 for regular app development. To update the gem for 2.0 2.1, I'd need to have both versions of Elasticsearch installed. While I could potentially do that by making a bunch of changes to the config files set up by Homebrew, I thought it would be better to just run the specs on a virtual machine and solve the "upgrade problem" indefinitely.

Docker looked great because I wanted to avoid machine setup as much as possible. I've used Vagrant before, but it has its own configuration steps beyond just "here's the Dockerfile." I already have boot2docker docker-machine installed and running for using the CodeClimate Platform™ beta, and I didn't want to have multiple virtual machines running simultaneously, eating RAM and other resources. Here's the setup:

  • Docker lets you run virtual machines inside "containers," using a few different technologies similar to LXC
  • boot2docker docker-machine manages booting virtual machine instances which will run your Docker containers. I'm using it to keep one virtualbox machine around to run whatever containers I need at the moment
  • fig docker-compose lets you declare and link multiple containers in a docker-compose.yml file, so you don't need to manually run all the Docker commands
  • The official quickstart guide for rails gives a good run-down of the tools and setup involved.

It's a bit of tooling, but it really didn't take long to get started; maybe an hour or two. Once I had it up and running for the project, I just modified the docker-compose.yml on my new branch. I had to do a bit of fiddling to get Compose to update the elasticsearch image from 1.7 to 2.1:

# modify the docker-compose.yml to update the image version, then:
docker-compose stop elastic
docker-compose pull elastic
docker-compose rm -f -v elastic
docker-compose run web rpsec # boom! builds the machines and runs specs

Once there, the specs started exploding and I was in business. Let the updates begin! After that, just a matter of pestering our CI provider to update their available versions of Elasticsearch so the badge on the repo will look all nice and stuff.

I wanted to try out basscss. Ended up changing the fonts, color scheme, and fixing syntax highlighting all over the place. Now using highlightjs, which looks like the only well-supported syntax highlighter than can guess which language your snippet is in.

This blog's been around for 5 years now. It's mostly thanks to Jekyllrb -- whatever I want to change about the site, I can do it without having to migrate from one database or format to another. If I need to code something myself, I can do that with Ruby or Javascript or plain 'ole shell scripts.

atevans.com has run off at least 3 different servers. It's been on Github Pages, Heroku, Linode, and more. It will never be deleted because some blogging company with a great platform ran out of VC, or added some social dingus I didn't want.

The git repo has been hosted four different places. When I got pwned earlier this year, the git repo was on the infected server. I just deleted it since my local copy had everything. Benefits of decentralized version control.

I had blogs all over the place before this, but this one has stuck around. I think even if Jekyll dies off somehow, a parser & renderer for a bunch of flat files should be easy in any language. I wonder what this will look like in another 5 years?


atevans.com in basscss

Web widgets are something everyone needs for their site. They've been done a hundred ways over the years, but essentially it's some bundle of HTML, CSS, and JS. The problem is that there are so many ways of doing this badly. The worst is "semantic" classes and JS hooks. Semantic-ish markup is used in quick-start frameworks like Bootstrap and Materialize, and encouraged by some frontend devs as "best practices."

Simaantec Makrpu

Semantic markup: semantic to who? It's not like your end-users are gonna read this stuff. Google doesn't care, outside of the element types. And it's certainly not "semantic" for your fellow devs. Have a look at the example CodePen linked from this post.

This CodePen represents the html, css, and js for two sections of our Instagram-clone web site. We have a posts-index page and a post-page ; two separate pages on our site that both display a set of posts with an image and some controls using semantic patterns. Some notes on how it works:

  • Does the post class name do anything? It's semantic, and tells us this section is a post. But that was probably obvious because the html is in _post.html.erb or post.jsx or something, so it's not saying anything we didn't already know.
  • What does a post look like? What styles might it have applied? Can't tell from reading the html. Is it laid out using float: left or text-align or inline-block or flexbox ? I'd have to find the css and figure it out.
  • Once I do figure it out, I need to look even further to see what post featured changes. Is featured something that only applies to post, or can featured be applied to anything? If I make user featured will that have conflicts?
  • Why are post and featured at the same level of CSS specificity? Their rules will override each other based on an arcane system no one but a dedicated CSS engineer will understand.
  • The .post{img{}} pattern is a land mine for anyone else. What if I want to add an icon indicating file type for that post? It's going to get all the styles of the post image on my icon, and I won't know about these style conflicts until it looks weird in my browser. I'll have to "inspect element" in the browser or grep for it in the CSS, and figure out how to extract or override them. What if I want to add a "fav" button to each post? I have to fix / override .post{button{}} . Who left this giant tangled mess I have to clean up?
  • Does .post have any javascript attached to it? From reading the html, I have no idea. I have to go hunting for that class name in the JS. Ah, it does - the "hide" behavior on any <button> inside the post markup. Again, the new "fav" button has to work around this.
    • What happens on the featured post? For the big post on your individual post page, a "hide" button doesn't even make sense, so it's not there. Why is the JS listening for it?
    • If I add my "fav" button, where do I put that JS? We'll want that on both the featured and the regular post elements.
    • What if we want "hide" to do different things in each place? For example, it should get more images from the main feed on the posts-index page, but more images from the "related images" feed in the related-images section. Do we use different selector scoping? Data attributes? Copy + paste the JS with minor alterations? The more places we use this component, the more convoluted the logic here will get.

Two steps forward, three back

Okay, we can apply semantic class names to everything: hide-button , post-image , featured-post-image , etc. Bootstrap does it, so this must be a good idea, right? Well, no. We haven't really solved anything, just kicked all these questions down another level. We still have no idea where CSS rules and JS behaviors are attached, and how they're scoped is going to be even more of a tangled maze.

What we have here is spaghetti code. You have extremely tight coupling between what's in your template, css, and js, so reusing any one of those is impossible without the others. If you make a new page, you have to take all of that baggage with you.

Solutions

In the rest of our code, we try to avoid tight coupling. We try to make modules in our systems small, with a single responsibility, and reusable. In Ruby we tend to favor composable systems. Why do we treat CSS and JS differently? CSS by its nature isn't very conducive to this, and JS object systems are currently all over the place (function objects? ES6 Class ? Factories?). Still, if we're trying to write moar gooderer code, we'll have to do something different.

I'm not the first one to get annoyed by all this. Here's Nicholas Gallagher from Twitter on how to handle these problems. Here's Ethan Muller from Sparkbox on patterns he's seen to get around them.

I've found a setup that I'm pretty happy with.

  1. Use visual class names as described by Ethan above.
    • Decouples CSS rules from markup content: an image-card is an image-card is an image-card
    • Makes grouping and extracting CSS rules easy - any element using the same CSS rules can use the same class names
  2. Name classes with BEM notation, and don't use inheritence.
    • No squabbles over selector specificity
    • Relationships between your classes are clear: image-card__caption is clearly a child of image-card just from reading the markup
    • Prevents class name clobbering: image-card highlighted could be clobbered or messed up when someone else wants a highlighted class, but image-card image-card--highlighted won't be
  3. Use .js-* classes as hooks for javascript, not semantic-ish class names.
    • Decouples JS from html structure - your drop-down keyboard navigator widget can work whether it's on a <ul> , an <ol>, or any arbitrary set of elements, as long as they have .js-dropdown-keyboarderer and .js-dropdown-keyboarderer-item
    • Decouples JS from styles. Your .js-fav-button can be tiny on one screen and huge on another without CSS conflicts or overrides
    • Clearly indicates that this element has behavior specified in your JS - as soon as you read the markup, you know you have to consider the behaviors as well
    • data-* attributes have all the same advantages, but they are longer to type and about 85% slower to find (at least, on my desktop Chrome)

This was brought on by using Fortitude on the job, which has most of these solutions baked-in. It had a bit of a learning curve, but within a month or two I noticed how many of the problems and questions listed above simply didn't come up. After using Bootstrap 3 for the previous year and running into every. single. one. multiple. times. I was ready for something new. I quickly fell in love.

The minute anyone decided to go against the conventions, developing on that part of the site got 10x harder. Reusing the partials and components with "semantic" markup was impossible - I had to split things up myself to move forward. Some components were even tied to specific pages! Clear as day: "do not re-use this, just copy+paste everything to your new page."

I'd much rather be shipping cool stuff than decoupling systems that should never have been tightly bound in the first place.

I made a bloggity post for Elastic, the company behind Elasticsearch, Logstash and Kibana.

Our list page got 35% faster. Then we took it further: Angular was making over a dozen web requests to get counts of candidates in various buckets - individuals who are skilled in iOS or Node development, individuals who want to work in Los Angeles, etc. We dropped that to a single request and then combined it with the results request. From 13+ HTTP round-trips per search, we got down to one.

Chef seemed like a big hack when I first started with it. Chef "cookbooks" have "recipes" and there's something called "kitchens" and "data bags" and servers are called "nodes." Recipes seem like the important things - they define what setup will happen on your servers.

Recipes are ruby scripts written at the Kernel level, not reasonably contained in classes or modules like one would expect. You can include other recipes that define "lightweight resource providers" - neatly acronymed to the unpronouncable LWRP. These define helper methods also at the top level, and make them available to your recipe. What's to keep one recipe's methods from clobbering another's? Nothing, as far as I can tell.

Recipes run with "attributes," which can be specified in a number of different ways:

  • on the node itself: scoped for a specific box
  • inside a "role:" for every server of an arbitrary type
  • inside an "environment:" for overriding attributes on dev/stg/prd
  • from the recipe's defaults

Last time I used Chef, attributes had to be defined in a JSON file - an unusual choice for Ruby, which usually goes with YAML. Now apparently there's a Ruby DSL, which uses Hashies, which also appear to run at the Kernel level. I couldn't get it to work in my setup. Chef munges these different levels together with something like inheritence - defaults get overridden in a seemingly sensible order. Unless you told them to override each other somewhere. Then whatever happens, happens.

"Data bags" are an arbitrary set of JSON objects. Or is the Ruby DSL supposed to work there, too? I dunno. Anyway, they store arbitrary data you can access from anywhere in any recipe, and who doesn't love global state? They seem necessary for things like usernames and keys, so I can forgive some globalization.

This seems like a good enough structure / convention, until you start relying on external recipes. Chef has apparently adopted Berkshelf, a kind of Bundler for chef. You can browse available cookbooks at "the supermarket:" are you tired of the metaphors yet?

The problem here is that recipe names are not unique or consistent! I was using an rbenv recipe. But then I cloned my Chef repo on a new machine, ran berks install, and ended up with a totally different cookbook! I mean, what the hell guys? You can't just pull the rug out like that. It's rude.

Sure, I could vendor said recipes and store them with my repo. Like an animal. But we don't do that with Bundler, because it seems like the absolute bloody least a package manager can do. Even Bower can handle that much, and basically all it does is clone repos from Github.

These cookbooks often operate in totally different ways. Many cookbooks include a recipe you can run with all the setup included; i's dotted and t's crossed. They install something like Postgres 9.3 from a package manager or source with a configuration specified in the munged-together attributes for your box. Others rely on stuff in data bags, and you have to specify a node name in the data bag attributes or something awful. Some cookbooks barely have any recipes and you have to write your own recipe using their LWRPs, even if attributes would be totally sensible.

Coming back to Chef a few months after doing my last server setup, it seems like they are trying to make progress: using a consistent Ruby DSL rather than JSON, making a package manager official, etc. But in the process it's become even more of a nightmarish hack. The best practices keep shifting, and the cookbook maintainers aren't keeping up. You can't use any tutorials or guides more than a few months old - they'll recommend outdated practices that will leave you more confused about the "right" way to do things. Examples include installing Berkshelf as a gem when it now requires the ChefDK, using Librarian-Chef despite adoption of Berkshelf, storing everything in data bags instead of attributes, etc, etc, etc.

Honestly, I'm just not feeling Chef any more. Alternatives like Ansible, Puppet, and even Fucking Shell Scripts are not exactly inspiring. Docker is not for system configuration, even though it kinda looks like it is. It's for isolating an app environment, and configuring a sub-system for that. Maybe otto is the way to go? But damn, their config syntax is weirder than anything else I've seen so far.

I'm feeling pretty lost, overall.

Everything you know about html_safe is wrong.

As pointed out in the World of Rails Security talk at RailsConf this year, even the name is kind of crap. Calling .html_safe on some string sounds kind of like it would make said string safe to put in your HTML. In fact, it does the opposite.

Essentially, you need to ensure that every bit of user output is escaped. The defaults make things pretty safe: form inputs, links, etc. are all escaped by default. There are a few small holes, though.

Safe

  • link_to user_name, 'http://hired.com'
  • image_tag user_image, alt: user_image_title
  • HAML: .xs-block= user_text
  • ERB: <%= user_text %>

Not Safe

  • link_to user.name, user_entered_url
  • .flashbar= flash[:alert].html_safe # with, say, username included

Fight for the Future wrote an open letter to Salesforce/Heroku regarding their endorsement of the Cybersecurity Information Sharing Act (pdf link). The bill would, according to FFTF, leak personally identifying information to DHS, NSA, etc.

The first sentence of the letter bothered me, though:

I was disappointed to learn that Salesforce joined Apple, Microsoft, and other tech giants last week in endorsing the Cybersecurity Information Sharing Act of 2015 (CISA).

Apple is proud of their lack of knowledge about you. They encrypt a lot of things by default. They have a tendency to use random device identifiers instead of linking things to an online account, which is better security but causes annoying bugs and edge cases for users. Tim Cook has specifically touted privacy and encryption as advantages of using Apple devices and software. The FBI has given Apple flack for using good encryption, and there were rumors they would take Apple to court.

Has Apple reversed their stance? Are they lying to their customers? I haven't seen them do that, ever. It would be really weird if they started now.

Oh, wait, they're not:

Microsoft and Apple, two of the world's largest software companies, did not directly endorse CISA. They -- along with Adobe, Autodesk, IBM, Symantec, and others—signed the letter from BSA | The Software Alliance generally encouraging the passage of data-sharing legislation. They also specifically praised four other bills, two of which focused on electronic communications privacy.

But who cares about the details, right? Get outraged! Get mad! Go the window, open it, stick your head out and yell: "I'm as mad as hell, and I'm not going to take this any more!"

The second sentence of the letter is also problematic:

This legislation would grant blanket immunity for American companies to participate in government mass surveillance programs like PRISM...

This implies a conflation I've seen around the internet a lot: that Apple willingly and knowingly participated in an NSA data-harvesting program codenamed PRISM because Apple's name appeared on one of the Snowden-leaked slides about the program. Also appearing: Google, Microsoft, Facebook, etc.

Apple responded that they did not participate knowingly or willingly. Google said the same thing. Microsoft spouted some weasel words; damage control as opposed to "what the fuck?!"

The NSA may have been using the OpenSSL "Heartbleed" bug for some or all of the data collection from these companies. Apple issued a patch for that bug with timing that subtly suggests it was in response to PRISM - pure speculation, but plausible.

Point is, if the three-letter agencies were using exploits like heartbleed, they wouldn't tell Apple or Google. To all appearances, Apple and Google didn't know anything about PRISM. The FFTF letter is making a weird insinuation that Apple, Google, and other companies would knowingly participate in such a scheme if the bill were passed.

I'm sick and tired of web sites, Twitter, news, etc telling me to be outraged. Virtually all of them reduce big, complex issues to sound bytes so we can get mad about them. I flat-out refuse to have any reaction (positive or negative) to anything "outrageous" I find on the internet, until I've done my own homework.

It started with TextMate when I first discovered Ruby on Rails in 2006 or so. TextMate went for ages without an update, Sublime Text was getting popular, and appeared to have mostly-complete compatibility with TextMate, so I switched.

Now Sublime has finally annoyed me. The Ruby and Haml packages just try too hard to be helpful, throwing brackets and indents around like there's no tomorrow, often in places I don't even want them. Time to try out Atom, especially since Github had a rather amusing video about it.

It takes quite a few packages to get up to the level I had Sublime at, but I think I'm basically there. Here's my setup:

  • Sync Settings - back up your Atom settings to Gist. Here's mine. Like dotfiles, these are meant to be shared. In Sublime this was a PITA involving symlinking things to Dropbox.
  • Sublime Word Navigation - nothing is more frustrating than having to hit alt+← twice just to get past a stupid dash.
  • Editorconfig - keep your coding style consistent.
  • Local Settings - I've wanted this in Sublime for ages. Simple things like max line length, soft wrap settings, and even package settings like "should RubyTest use rspec or zeus" on a per-project basis.
  • RubyTest - speaking of... Does everything I need from Sublime's RubyTest, just had to re-map the keyboard shortcuts.
  • Pigments - shows css colors in the editor, and alternative to Sublime's GutterColor.
  • Aligner - works way better than Sublime's AlignTab package.
  • Git History - step through the history of any file.
  • Git Blame - shows the last committer for each line in the gutter. Unfortunately, the gutter is too small for many names, so it craps out and shows "min". Also, the gutter can't keep up with the main window's scrolling, which is janky.
  • Git Plus - I still end up doing Git on the command line. This often didn't support the stuff I need to do on a daily basis.
  • Language-haml - if you're unfortunate enough to have to deal with HAML, this kinda helps. Like putting a band-aid on a bullet wound.
  • Rails Transporter - this is a nice idea, but it still doesn't cover the functionality that Sublime's RubyTest had. cmd+. would let you jump from a file to the spec file and back, and transporter just gives up if you're in a namespace, form object, worker, etc.

How's it working out? Well, Atom still feels a bit unpolished overall. Some of the packages above don't work quite right, or aren't as helpful as they advertise. And Atom's auto-completion is annoying as bloody hell. It seems to use CTAGs or some variant, so it pulls in all symbols from everywhere, and the one I want is never even close to the top. And it pops up on every. single. thing. I. type. in a big flashy multi-colored box that randomly switches whether it's above or below the cursor.

Finally, the quick-tab-switch is terrible compared to Sublime's. It's fuzzy matching is way worse, it ignores punctuation like underscores, and definitely maintains no concept of how "nearby" a file is, nor how recently I've opened it.

I might switch back.

Or: I'm getting too old for magic tricks

So much of what we try to do is get to a point where the solution seems inevitable: you know, you think "of course it's that way, why would it be any other way?" It looks so obvious, but that sense of inevitability in the solution is really hard to achieve.

~ Jony Ive, July 2003

I've been doing Rails for nearly a decade. I've seen bits of magic come and go, I've written too-fancy abstractions that leak like sieves, and mostly I've worked both solo and on teams. I've come to like boring code. Code with little to no magic, that looks "enterprisey," that has too many classes and objects, and uses boring old things like inheritence instead of composition.

Boring code is easy to read, and easy to debug. When you don't define methods and classes dynamically, you can actually use the stacktrace. When you don't use mixins, modules and concerns, you never have to wonder where a method is defined. You can grep your codebase. When you separate domain logic from the underlying technology, it's very clear what is happening where.

That's very helpful for working on teams. Everyone should be able to read and understand your code. The ability for someone else to understand and work with your code has an inverse, exponential correlation with the number of files, objects, and messages between input and output. Layers of indirection and metaprogrammed magic make the curve even steeper.

I want to make it really hard for the most annoying, stupid member of my team to screw it up: future me. Me in three months, when I've lost context and forgotten why I wrote any of this, or how. I want him to pick it up. Maybe he'll say "man, this code is stodgy," but he'll understand it immediately.

Let's get to work.


No side effects in model code

Code in your models should not change any other models, send emails, call APIs, or write to anything other than the primary data store. Especially in callbacks.

Callbacks are great for setting and verifying internal state. A callback to normalize a url, email, or url slug is great. You're just ensuring the model's data is consistent. A callback to send an email is total bullshit. There will be times, probably many of them, when you do not want to send that email. Data migrations, actions from admins, a hundred other cases. Put those actions in another class, or make a method that is never called automatically. Force yourself to be explicit about when that is happening in your controllers, background workers, etc.

Of course there are exceptions. touch: true is generally fine, as long as the touched model has no side effects on update.

No observers

Observers were removed in Rails 4 for a reason. They are invisible logic that no one knows to anticipate. Use explicit calls in controllers or workers.

No default scopes

When you write an ActiveRecord query, you should see exactly what it does. No one should have to wonder why they are getting unexpected ordering, joins or n+1 queries.

No state machines for models

Everyone thinks this state machines for your models are a great idea, and I've no idea why. Look at all these state machines. These put your business logic inside your models. That's great, right? I mean, it gets them out of the controller. But models are not your junk drawer for business logic.

Models will get to invalid states, as inevitably as the fucking tides. The business logic will change. You will deploy bugs. Then you have to do some ugly hack like update_columns status: 'fml' to herd them back into line. You have to do a ton of setup in tests. State machines define tons of magic methods. Guard methods, state-specific methods, and transitions will fail.

State machines are for in-line processing. Regular Expressions are a great example. They are not for asynchronous changes over time that sync to an external service like a database.

Just use a bloody string field, or better yet an ActiveRecord Enum. You can use conditional validations, but really you should put your business logic elsewhere.

Avoid instance variables in views & helpers

I write partials like this:

# app/views/blog_posts/_byline.html.erb
<%
  post = local_assigns[:post] || @post
%>

<div class="byline">
  <span class="author-avatar"><%= fetch_author_avatar(post.author) %></span>
  <span class="author-name"><%= post.author.name.titleize %></span>
  <span class="post-date"><%= localize post.updated_at %></span>
</div>

# app/helpers/blog_posts_helper.rb
module BlogPostsHelper
  def fetch_author_avatar(author)
    CDNFetcher.generate_url(author.avatar_url)
  end
end

Even that's not great, since post.author may be an n+1 query, but that's manageable with the Bullet gem.

Explicitly declaring variables and passing dependencies downward makes it crystal clear where everything is coming from. When you want to render this partial in some other view, and you inevitably will, you won't have to dig through the whole chain and figure out what to set in the controller. Instance variables are effectively global variables for the view scope, and nobody likes globals.

Locals are excellent for making sure your partial doesn't depend on instance variables, but they're bloody annoying when it isn't clear where they're coming from. The local_assigns hash prevents cryptic undefined method errors, makes the partial's dependencies explicit, and allows you to override them when you're using the instance variable for something else. I even pull a local out of this hash for the partial-name-variable passed in with render partial: 'my_partial', object: obj - byline in this case. This allows for defensive coding, sensible defaults, and makes it an explicit dependency.

Helpers that depend on instance variables are less clear and less reusable than helpers with arguments. They compound the problem of instance variables in views or partials, since they're not immediately visible when looking at the view code.

No view helpers in models or controllers

"Convention over configuration" is one of the huge benefits of Ruby on Rails. You don't wonder where to put this or that bit of code, and other devs don't wonder where to find it. If you have a method on a model that formats a name so it can be used in a view, you've made it harder for anyone else to find. Same thing if you define a helper in a controller that is used in the view.

Use additional conventions

Some really smart people in the Rails community have invented more specialized objects for parts of a Rails app, and they had some good reasons. Form Objects, Service Objects, Presenters, and other conventions exist to help you keep your code clean and DRY.

Don't always or dogmatically use these things - a form to update a string in a model doesn't need a form object. A controller that saves one model and makes an API call doesn't need a service object. But when code gets re-used or specialized, these can be super helpful. Having more conventions for your team helps keep it obvious where any given piece of code is or should be.

Don't go too far, either - I think Trailblazer or Hexagonal Architecture make it harder for Rails devs to understand where things are, and tempt you into using more magic to wire everything up.

Remember that abstractions hurt

All abstractions leak, and these are some of the most aggravating bugs to deal with. You end up pouring through someone else's source code trying to figure out what the hell is going on. Not to pick on Trailblazer (it really does look interesting), but when I saw the contract / validation DSL I immediately shook my head. Knowing when something is invoked and how is pretty important. The more of that you have to keep in your head, the less working memory you have for actually writing your code.

To justify an abstraction, it has to have 10x easier than operating without it. Not using the abstraction has to be so painful that you're actively losing hair over it.

For example, this is my main issue with HAML. It's a big abstraction - it takes you very far away from the actual HTML you want to render - and the only value it provides is "it's pretty." And it's not even pretty, for non-trivial apps. If you use BEM notation, any amount of data attributes, conditional classes, or I18n, you end up with perl-like punctuation soup. You can't even add arbitraty white space to make it more readable.

Sass (in its scss form) is a great counter-example. Lacking variables, comprehensions, and clear inheritence is a massive pain when writing css. Sass keeps you pretty close to the generated css, and provides 100x the power.

DSLs, Concerns, transpiled languages, and syntax sugar gems are all suspect. Be mindful about when and how you introduce new layers of abstraction.

Don't monkey patch

Duh. Use Decorators to make it explicit where your methods are coming from.


These are all very general guidelines. Rules are meant to be broken, and you totally should if it makes your code 10x easier. I'll add more if I can think of anything else.

Next
1 2 3 4 5
... Last