The ups and downs of Rails magic


"You're a wizard Harry!". Graphics courtesy of undraw.co

I love using Ruby on Rails to code websites - being a well-established web framework, it makes the process quick and easy. Not to mention I find it a joy to code in Ruby! But we recently moved to a completely JavaScript stack at the office (Node.js on the backend and React on the front-end) and that’s opened my eyes to the ups and the downs of working in a Rails-based environment.

TL;DR


Rails works well if you need to bring something from idea to finished product fast. Use something like JavaScript, if you want more clarity and control.

What the heck is Rails “magic”?


Coding is a science!

I hear you and to that I say, “It can sometimes feel like magic ... you muggle 😏” - I mean, just look at AI! Rails magic is what I’d describe as the things that Rails does for you. It eliminates “boilerplate”/standard code and allows you to focus on the stuff that sets your product apart from everything else. 

Running a web-app is complex - you need to set up a server environment, make sure your controllers, models and other code are wired up properly etc. Rails can feel like magic, because you don’t actually do any of that stuff and it just works, because Rails handles all that for you.

How does that work? 

Rails follows an ideology of “convention over configuration”. This means that in the Rails world, code files must be arranged in certain, predictable folder structures or code must be written in certain ways - i.e. there is an established convention for how Rails wants you to write that code. This makes it easier for certain features or aspects of building a web-app to be automated, since it’s clear where the code must be added/changed and what must be done. 


Sounds good so far, tell me more!



Rails can help you get going on a web project much faster than most other frameworks out there. In fact, if you don’t really care about styling, you can have something like a functioning blog (something that can put posts on the internet) up and running in about 15 minutes. How does it do that? Well, you just type in the following few lines of code into your Terminal and voila, you have a blog:

# Generate the blog project, in this example, we call it “blog” and set the database to postgres
rails new blog -d postgresql

# Add the ability to create, read, update and delete posts, where each post has a title and a body
rails g scaffold Post title:string body:text 

# Make sure your posts are saved to the database
rails db:migrate

You now have a functioning (if ugly and somewhat insecure) blog. It’s ugly because it uses Rails’ default styling, which uses a lot of tables and doesn’t really care about looks. 

Quick note here, when I say insecure, I just mean you don’t really have user accounts, meaning that anyone can post on your blog etc. I do not mean that it’s easily hackable - Rails takes security quite seriously and builds in many features that help establish better security out-of-the-box.

In addition to doing all this stuff for you, Rails also hides a lot of the boilerplate code, so your code base looks much simpler and more focused on what you want it to do. Here’s an example: 

Let’s say you have a posts controller. The code to get the details of a single post in a node.js application (built using the express.js package) would look like this:

async getOne(req, res, next) {  
  const post = await Post.findByPk(req.params.id)
  if (!post) return res.status(404).send('Post not found')
  return res.status(200).send(post)
}

In Rails, it would look like this: 

def show
end

Rails has already handled all the code related to grabbing the required post and leaves us to simply decide what to do once we have it - which in most cases, is nothing, so it just sends that data to the front-end!


Now, let’s talk about user accounts - what if you want to add those, is that easy too?


Enter, gems 💎!


Almost like infinity stones 😬


Yes, it is! I’ve generally used a Rails gem (that’s what the Ruby programming language calls plugins/packages) called Devise to get my user accounts and authentication set up. 

Being a web framework that is well used (websites as big as GitHub, Fiverr and Shopify are built on Rails), there are a ton of useful gems that each help add functionality that the typical website may need. 

Do you need your forms to accept data for nested models? Use Cocoon. Need to easily handle environment variables between your development and production systems? Use Figaro. Need an admin dashboard? Use administrate. What about pagination? Use Kaminari. You get the point.

A lot of these gems are (almost) plug and play, because, like Rails, they handle a lot of the complex stuff for you and expose a few simple APIs that you can use to add their functionality to your app. That doesn’t mean they aren’t customizable - many of these gems are built to allow you to use them the way that your app needs them to be used. 


That’s cool, but what if you want more automation!


Rails can help there too. Rails introduced a concept called “templates” a while back, which essentially allows someone to package together certain gems and write some code to handle the set up of those gems, so that you can not only generate a basic rails app when starting a new project, but you can add all sorts of functionality (like user accounts or bootstrap) right out of the gate.

One template I’ve been very interested in has been the Jumpstart template. It essentially allows you to add user authentication, announcements, an admin dashboard and more with just 1 line of code in your terminal:

rails new myapp -d postgresql -m https://raw.githubusercontent.com/excid3/jumpstart/master/template.rb

What if you don’t need an entire app, but just need to set up a small part of it? Check out RailsBytes (co-created by the developer for the Jumpstart template above) - these put together gems to do little tasks, like a quick template to add and set up Bootstrap with Webpack in Rails 6, for example. Once again, just running something like the following in your terminal is all you need to get going:

rails app:template LOCATION='https://railsbytes.com/script/x9Qsqx

Pretty cool, right?

...

If you haven’t noticed already, all of these benefits lead to faster project completion and more focus on your unique business logic.


Now, about those downsides


A lot of the downsides I’m going to list down are from the perspective of a beginner to intermediate programmer. Given I have some experience with building web-apps with JavaScript, I’m going to use that as my source for counter examples to Rails.


If you need customizability in your projects, maybe don’t use Rails


Just like making your own sandwich 😋


Remember when we talked about “convention over configuration”? Well, what if you want your project to be configurable (i.e. customizable)? Let me give you an example.

When you initiate a Rails app, you get every part of Rails whether you like it or not. What if you just want to build something simple? All that extra stuff that Rails gives you for more complex projects could weigh down your project. 

In the JavaScript world, you can pick and choose what components you add to anything. You can even pick and choose what your folder structure and file names are going to be - it’s completely configurable. You are completely in control.

This added customizability helps reduce the size of your application and keep things to just what you need. Not to mention, you can build it however you want!


Rails code may not afford the most clarity



Take a look back at that posts_controller code we had above:

def show
end

If this is your first Rails project, it really isn’t clear what’s going on here. Do I have to add more code for it to work? Do I have to look elsewhere for the code for this?

But if you look at the JS code, it’s very clear that we are fetching the database record for the post id provided and sending that to the front-end. 

Abstraction like this is one reason Rails can feel like magic, but it’s also quite confusing at first and creates a bit of a learning curve when getting into Rails. Which leads me to the next downside.


It can be bad for beginners



Don’t get me wrong, Rails is one reason I’ve built many of the things that I’ve built and had them in “shippable” quality, but I must admit that before working with Node I didn’t have a good understanding of what the framework was doing or why it did things in certain ways. This led to too many instances of code copied from StackOverflow or mindlessly copying code from a gems documentation to get it working. This also meant that when I moved to Node, I had to learn a lot about best practices and configuration.

In other words, if you’re a beginner, then using a framework like Rails (where things are done for you) it becomes easy to pick up bad coding habits, since the framework does a lot of the heavy lifting and keeps things working anyway (This is, of course, assuming you’re working alone and don’t have someone to check your code and all that).

Where this added knowledge really comes in handy is debugging - it’s difficult to fix something that’s broken, if you’re not sure why it broke in the first place.

“I copied the same code from the docs, why isn’t it working?!”

For example, take nested data in form submissions - where we take data for an associated model through a form for another model - Rails can handle this for you like so: 

# In app/models/my_model.rb
accepts_nested_attributes_for :other_model, allow_destroy: true 

# In app/controllers/my_model_controller.rb params.require(:my_model).permit( :name, :description, other_model_attributes: [:id, :heading, :body] )


In JS, some of this is manual/needs to be specified manually:

// Send back data for the nested model
for (const item of req.body.otherModelAttributes) {

  // For each item of data run through the following process
  const associatedRecord = await joinModel.findOne({
    where: {
      myModelId: myModelInstance.id,
      otherModelId: item.otherModelId
    }
  })

   if (!associatedRecord) {
    // If the association doesn't exist, then create it
    const newAssociation = {
      // Association attributes
    }
  await joinModel.create(newAssociation, { transaction: t })
  } else {
    // if it does exist, then update it
    await associatedRecord.update(
      // attributes to update
    )
  }
}
 

Having to go through this more verbose process gave me a better understanding of what was happening under the hood, which even helps me with debugging and building Rails apps. Such things may slow down a project or make it seem difficult, but it helps to truly understand what you’re doing, instead of just cobbling together a few packages to get things working.

...

I still love developing in Rails, but working with JavaScript has given me a deeper understanding and a deeper appreciation of what Rails does. All this to say, it’s important to try out and learn different programming languages and frameworks, as each can provide a nugget of information that may be helpful for developing on any language. 

This felt like a more generic post (like “Node vs. Rails”), but I hope that it’s given you some insight into what Rails magic can and can’t do for you. Until next time, happy coding!