My day job is developing a Ruby-on-Rails application. I’m won’t pretend to be a Rails fanboy by any stretch, but it does allow for rapid development which is useful in start-up life, and it’s good enough for now. Ruby is a dynamic language with a rich standard library. The Rails platform is extremely rich too, known for its convention over configuration, which allows one to side-step a certain amount of boilerplate, at the expense of the additional cognitive load of having to remember the conventions. To make my life a little easier, I’ve opted for an IDE: Jetbrains RubyMine to be specific.
I’m not getting in the IDE vs editor debate, other than to say I use both, and I perhaps tend slightly towards IDEs whenever the project is a little more complex. That habit probably formed when I was primarily a Java developer, and the features on offer for a strongly typed language really shine.
Jetbrains has been dominant in the Java IDE space for years with Intellij IDEA for understandable reasons: it’s a jolly good tool. Jetbrains has expanded and created new IDEs tailored for other languages including C#, Go, Php, Python and Ruby. These things cost a significant amount of cash via annual subscription (although for some languages they do offer free, stripped down “community” editions).
Jetbrains conducts a survey every year and got into a bit of trouble in their analysis of the Java ecosystem when they noted that Microsoft’s free, yet excellent, VS Code editor was growing in popularity:
“VS Code is growing which is concerning, not from a competitive point of view but actually from the point of view that there is clearly a lack of understanding of what an IDE gives you. VS Code is a code editor with some features that you’d find in an IDE, and extensions that can provide additional functionality — so if people are turning to VS Code for developing it may imply that developers don’t know what a fully-featured IDE can give them. In the web space it is understandable to use an editor as web developers are typically working with dynamic languages, and often use other tools like browser plugins to give them what they need. But in Java, especially professional Java, you really get a lot out of a good tool that has integration with the application server and you can really use the analysis and refactoring and everything.”
In the context of Java, I don’t think it’s a stretch to believe that an IDE like Intellij can bring an awful lot to the table to make development more productive, and that is the context of the above quote. However, I’m not sure they could be so confident in an analysis with the Ruby ecosystem, because quite frankly, their RubyMine IDE is nowhere near as good Intellij, and in fact in terms of raw code editing, it can frequently hinder the developer. Perhaps I’m in a grumpy mood, but I frequently find myself wondering whether anyone at Jetbrains actually dogfoods RubyMine for their day job.
I’ve recently started a list called “RubyMine woes” where I itemise instances where RubyMine doesn’t support my development efforts. I’ll only cover the first six in this post, and I’ll no doubt do more posts.
Unhelpful preselected completion
Let’s say we have a basic controller in our Rails app. Idiomatic Rails says that for a full set of CRUD operations I need to follow the convention of having new, create, edit, update and destroy methods. Of course, it’s possible to use additional and/or alternative methods, but for typical project, the top six method names in a controller will be
Watch what happens as I try to write stubs for the
def create<ENTER> without pause or hesitation. But because it already had a candidate suggestion for a method I could override that also began with ‘create’ — a completely irrelevant suggestion — it presumed my ENTER wished to accept that suggestion.
It’s quite typical for editors to assume you are selecting the suggestion upon ENTER, but it tends not to be a bother because they are auto-completing existing variables, functions or classes. In this case, RubyMine is getting in the way of the developer when he/she is declaring a new method. And as we’ll see later, given how many potential overridable method names it knows of, it’s a complete distraction.
Jetbrains do not consider this preselection a bug. Quite the opposite: this is a fearure and has its own dedicated preference within the Code Completion settings and is enabled by default. Alternatively, you can use the oh-so-convenient SHIFT+ENTER to override the preselect or I could press ESCAPE before ENTER to deactivate the suggestion.
The other slight peeve in this example (of method names for controllers) is that despite this being a common Rails convention (and RubyMine does know about some of them) it doesn’t have any helpful generate/autocomplete options here.
Method Override Noise
So why exactly did RubyMine offer
create_shell_runner as a possible suggestion in the first place? This method comes via the
rake gem that is a Rails dependency. The rake gem?! That’s the Ruby equivalent of Make for running tasks, and Rails uses this a lot for project initialisation and set-up, it’s not intended to be part of a Rails application runtime.
I certainly haven’t read through the Rails source but I’m pretty sure they are not extending any of Rake’s classes. However, a glance through the Rake source I noticed:
# Extend the main object with the DSL commands. This allows top-level
# calls to task, etc. to work from a Rakefile without polluting the
# object inheritance tree.
That would explain why I’m seeing loads of Rake DSL methods and FileUtils methods as overridable. Ruby has a pretty verbose object hierarchy already, and it quickly swells up because of the way developers love to monkey patch existing classes. A “clean” Rails project may pull in 70+ gems before you’ve even started, and before you know it base classes like
Object have been extended with methods galore.
And what do you know, RubyMine’s parser is churning away and indexing all this stuff. It works wonderfully for Java; but becomes extremely noisy for Rails developers.
def and look at the list of suggestions it has. It’s massive. (Ruby allows you to override private methods so those don’t get filtered out.)
Strangely, and this is an odd complaint for me, but it’s not even complete. Rails adds the method
html_safe? to the
Object class. It’s actually something a Rails developer may wish to override unlike all those FileUtils methods. But it’s not showing as an overridable method. One needs to go to the dedicated Override Method helper and search for it.
Note just how huge and disorganised this list is. (You can just start typing and it’ll filter the list).
This method override feature (whether the autocomplete or dedicated helper modal) works very nicely in Java-land, but it doesn’t translate into an easy-to-use feature with Ruby. In the real-world any decent sized Rails project will be swamped with hundreds if not thousands of methods in that list.
As a result Jetbrains actually needs to actually apply some common-sense to this issue. I understand that RubyMine’s just following the code, but that’s not enough: for RubyMine to actually be useful to Rails developers, it needs to find a way to allow developers to avoid all this noise.
Rails expects all the application routes to be declared in a single file. Because routes are an essential part of a Rails application, RubyMine has some understanding of the routes system. As a result it should be able to detect more than just simple syntax errors, e.g. if there’s a route for which there’s currently no controller. Very helpful. If it worked.
Let’s illustrate via an example in which I need routes for managing a User. I will use the convenient Rails shorthand of a “resource” to create all the necessary routes for the standard CRUD paths for creating, updating and showing users. In addition, each user can have a single, sub-resource of “profile”.
resources :user do
Despite the route for profile being singular e.g. (/users/1/profile) by default it maps to the
ProfilesController (note ‘Profiles’ is plural) as we can see from Rails’ own routes output.
Oh dear, RubyMine isn’t happy though as it expects ProfileController.
Next, let’s stick a new “posts” resource in. RubyMine correctly identifies that I don’t yet have a corresponding controller. No problem, like any good IDE, I’m sure that if I open the Quick Fixes bar it’ll offer to create this missing file.
Ah, ok. Perhaps not. It’s not a terribly useful set of options at the best of times, and doesn’t resolve an easy, low-hanging productivity gain.
The other big reason why RubyMine parses the routes is because each one has a corresponding helper “prefix” that developers tend to use. E.g., if I wish to link to the profile of user with id=1, I can use
user_profile_path(1), and that translates to
/users/1/profile. RubyMine can offer these url helpers whilst I type. And for a large project this really can be helpful, assuming RubyMine is accurate.
Unfortunately, it seems it doesn’t like plural namespaced routes. For example, let’s wrap a ‘posts’ resource in a ‘reviews’ namespace.
namespace :reviews do
Here’s how Rails interprets this route:
So the url helper for a new review post would be
new_reviews_post_path. However, RubyMine’s autocomplete has ‘review’ as a singular. There’s no convention at play here whereby namespaces have to be singular, it’s just an incorrect assumption that RubyMine has.
From the command-line you can call
rails routes which loads in the routes configuration and outputs the true set of URIs, prefixes and controller mappings. It’s darn slow, but I have to frequently call that rather than trust RubyMine’s interpretation.
Note: apparently there’s a major revamp of routes coming soon that should fix these issues. But c’mon, it’s almost the end of 2020. I guess I’d rather hoped that RubyMine might have nailed this in, say 2014.
Let’s pretend we need to run some code against the profile, and we intend to put it in a little helper method called
Well, at this stage, RubyMine is spot on: it cannot find the method
do_something because I’ve not written it yet. So let’s make it.
It now exists as an instance method of the
Things class. But RubyMine now thinks this is sufficient to resolve the original error in
By pure fluke, I noticed that the variable
profile wasn’t being flagged as unresolved too. It should be because it hasn’t been declared. So why not? Ah, because the ‘selenium-webdriver’ gem happens to contain a class with an instance variable also called
It’s out of scope, but nonetheless reinforces the notion that RubyMine’s code insights are not terribly insightful. I find this stuff anti-productive, because in the previous screenshot, the IDE, with all the power it extols, gives the
ProfilesController a nice green tick: no errors here!
Resolved references — part 2: through Rails associations
Shifting now to a new level of flimsiness. Let’s imagine I have some posts and images, and I’d like users to be able to comment on either. I could create a new
ImageComment, or I could create a single
Comment and use Rails’ built-in “polymorphic” associations. It means I have one table modelling the comments, and because I’ve used the key name “commentable”, there will be two columns called
commentable_id, and with this Rails will allow me to associate the
Comment with any other model should I declare it. (They’re not everyone’s cup of tea, but they are a convenient shortcut.) This is how it is setup in code:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
class Post < ActiveRecord::Base
has_many :comments, as :commentable
class Image < ActiveRecord::Base
has_many :comments, as :commentable
Now let’s imagine in the same project I’ve got a little service object that may operate on an instance of
Image. Or you could ignore that because the backstory doesn’t actually matter. So here’s a bit of code.
The error here is on line 7: the variable should be
@commentable (with the @ symbol) not
commentable, but RubyMine doesn’t flag it. In fact, if I hover over the undefined variable, it shows me what it knows about it. It thinks that it’s something to do with the
Comment class because it declares the
belongs_to :commentable association. Even if you think “Hm. Nice guess”, why does the existence of this ActiveRecord association declaration satisfy RubyMine into thinking that the invalid
commentable variable is in fact resolved?
Again, this is not a bug for Jetbrains. 😳 Apparently, one can improve this by altering a setting, that on the one hand will finally cause it to detect the unresolved variable… at the expense of lighting up every other line of code in the project with false warnings. Either way, it’s noise all the way down.
Types in doc strings
The reason Intellij can be so much smarter is because it’s operating on a strongly typed language. RubyMine will always be constrained until Ruby adopts type annotations. But as luck would have it, RubyMine supports Yard doc strings and if you annotate your methods with appropriate doc strings, it will gladly leverage that information.
Alas, what a shame that despite having a quick-fix option to add param tags, it isn’t able to suggest the two common data-types:
Integer. In fact it doesn’t provide
Hash either. Petty? Sure, but again, it illustrates the problem that this IDE is not fully charged up for the job it purports to do. This is IDE bread-and-butter. It’s trying to suggest stuff; given that I’d expect to see
WIN32OLEQueryInterfaceError. What else is it missing?
Ok, well let’s see how well this info is in fact leveraged. Let’s create a class that accepts an
Array when initialised and then the incoming parameter as an instance variable.
It’s possible to view RubyMine’s inferred types by hovering over each variable.
At line 4 RubyMine knows
@items is an array. By line 8, it doesn’t have any type information on
Contrast this to Visual Studio Code. VSC correctly holds the type information and provides useful, relevant suggestions, i.e., actual
Array methods. 😱
In fairness, it is possible to get this working in RubyMine by adding another yard doc instance variable annotation. So that’s ok — just twice the effort required compared to VSC.
I could write an article about what RubyMine does well. It has a good debugger. It’s a breeze to search and move around projects. I18n support is useful. Setting up a Rails project isn’t terribly difficult, but if you use RVM then it certainly makes it even easier to set up your ruby version and gemsets. Its Ruby syntax highlighting is probably one of the better ones. In many respects, the figurative cheque I write every year is my favourable review.
I don’t envy anyone who tries to make an IDE around Ruby, or any dynamic language for that matter. However, if you are going to do it, then you’d better ensure it’s geared around developer productivity. It’s very hard not to be distracted by the code editor when it’s throwing up weird suggestions. It’s also very hard not to despair when it’s missing easy to catch errors, whilst also flagging perfectly valid code with warnings. It does not instil confidence. The ace up the sleeve of most IDEs are its suite of refactorings. (RubyMine does not have an impressive suite.) It’s a tense moment when you run a method rename.
I find editors like Visual Studio Code perfectly reasonable: decent editing and in addition good auto-complete suggestions and easy navigation/lookup of class/method/variable declarations. They won’t flag syntax errors, but then it doesn’t report false positives either. It’s not pretending to offer insights therefore I can’t be disappointed if I don’t get any. (Although I’m sure there’s handful of plugins available to make it more insightful.) That said, if you haven’t tried Typescript development in Visual Studio Code then I think it’s a model of what can be achieved with a dynamic language with type annotations.
In the meantime, if RubyMine isn’t going to bring anything else to the table to make the Ruby experience more palatable, Frankly I’d rather it dialled down its insights if they are going to remain this flimsy. I suppose there’s an option already to disable them.
I wish it offered better intelligence for Rails projects too. I’ll probably cover this in a dedicated post. As I’ve said, it has some Rails-specific tools and features, e.g., I really like the ease at which one can quickly switch between a controller method and its associated view (and vice versa) — to do that it needs some built-in logic about Rails. It’s got a bit of logic w.r.t. models/schemas and routes but lacklustre at best.