Helping devs understand concerns faster

One thing that took me a while to wrap my head around is concerns. Part of that is just that I hadn’t really used them before and they’re more of a Ruby (and Rails) thing that with other languages. At the same time, they’re SUPER powerful and helpful. It feels like people could see the power of Rails better if they understood Rails and concerns.

There are a set of things that could help with this:

  • Putting a concern focused guide together on guides.rubyonrails.org. There are a number of places where concerns are mentioned but they don’t really explain why you’d use a concern, how to use them, how concerns are already used in Rails and which concerns (and what they do) are available in Rails already for use by developers
  • Have a generator for concerns. Concerns, like other parts of a rails application, belong in a particular place in the file hierarchy app/*/concerns, should have a particular name and structure and should be in the concerns subdirectory of the part of the application they relate to, i.e. a controller concern should be in app/controllers/concerns not app/models/concerns.
13 Likes

Really agree with this. I’ve been writing Rails nearly 8 years. I’m not convinced I understand how to use Concerns correctly. As a result, I don’t recommend them to colleagues/Rails newbies.

ps. I know it’s not really a Rails problem, but Concerns don’t play nicely with sorbet at all. Which also makes them hard to recommend, since we’ve really fallen in love with sorbet.

1 Like

+1 on adding a generator for concerns, that seems worth doing.

I found the DHH coding videos pretty eye-opening as to how concerns are used in a very concern-friendly environment.

5 Likes

do you have a link to those videos?

2 Likes

Looks like the preferred home is The Getting Real Basecamp YouTube channel.

4 Likes

On the other side, I’ve been on Rails for about the same duration, and I couldn’t imagine my life without them. I will break out into concerning :X do; end at the snap of fingers.

There really seems to be an information gap here
as there is one with Git :eyes:

1 Like

Hah, I thought this thread was about ActiveSupport::Concern

TIL about Module::Concerning

2 Likes

I was thinking of ActiveSupport::Concern but Module::Concerning is also part of this too.

Well, yes, Module::concerning does create a module extending ActiveSupport::Concern.

They’re all concerns, which are basically just fancy modules.

IMO, the problem with concerns, is that they are really nothing special, they are just plain Ruby modules. But the fact that a new word was introduced creates an expectation that you’re missing something. Which is something I totally understand.

A concern is a module that you extract to split the implementation of a class or module in coherent chunks, instead of having one big class body. The API surface is the same one, they just help organize the code. A concern is a regular Ruby mixin, there’s nothing else, it is not a new concept. It is plain Ruby.

So, when you extract pricing-related API from a Hotel class, you typically do something like this:

# app/models/hotel.rb
class Hotel
  include Pricing
end

# app/models/hotel/pricing.rb
module Hotel::Pricing
  # Hotel pricing-related logic.
end

There, Hotel::Pricing is a concern. But that idiom is not Rails-specific, this is vanilla Ruby programming.

Directories like app/models/concerns are meant for mixins that can be reused, in this case, in the model layer.

On the other hand, AS::Concern is orthogonal. A concern is a Ruby module used in a certain way (vs used as a namespace, say). Ruby has the included callback that gets fired when a module is included somewhere, and you receive the enclosing class or module as argument, that allows you to decorate it, for example. Well, the included DSL macro is essentially the same thing, except that the decoration looks prettier. It is a DSL for mixins that want to decorate their enclosing classes or modules.

But, perhaps surprisingly, a mixin does not need to extend AS::Concern to be a concern. Or to call concerning. A concern is a Ruby module with a certain purpose, as explained above, and whether you use those DSLs or not is irrelevant.

And you see the problem, concerns are so simple that they do not deserve a full guide. Concerns are mixins, if you are a Ruby programmer, you already know what a mixin is and their use case to modularize APIs.

But at the same time, a post like this is needed to explain that they are just that!

That means there is something to address here, I believe we are missing a definition, but don’t know exactly how to fix it.

6 Likes

I think there’s a lot of Rails users that don’t know this. I think there’s a decent number of Ruby devs who know vaguely about mixins but they don’t use it because they don’t understand it. If you combine teaching folks how to use and create concerns properly and documenting the concerns that are already in Rails that folks can use, I think there might be enough for a guide.

I still think there’s value in a generator or something sort of assistance for helping people use concerns in a consistent way with a convention.

1 Like

Maybe, but you have to draw the line somewhere, in my view it is out of the scope of Rails documentation to teach you Ruby. As a Ruby programmer, you need to learn Ruby.

As I said above, I believe there’s something to address, this thread is a proof of that, and I’ve seen others like this. But I don’t know how.

Regarding a generator, which usage have you in mind? How does the command look like? What does it generate?

I mean, if we’re here to talk about getting folks up to speed on using Rails properly I don’t think this is a great attitude to have. I get the concern (no pun intended) but I wouldn’t exclude it as something the community should evaluate.

My first though is something like this: rails generate concern name_of_concern type_of_concern --class_methods class_method_names --included_methods included_method_names where type_of_concern is model/controller/job/mailer/etc

This would:

  • Place them into the app/type_of_concern/concerns directory
  • Create a test for the concern which includes stubbed tests for each of the class methods and each of the included methods. It would also include a stubbed version of the model/controller/job/etc for including and testing the concern.

The dependency-resolution between modules goes beyond mere syntactic sugar, IMO.

A concern does not have syntatic sugar per se, like the Hotel::Pricing module above. The syntatic sugar is a feature of AS::Concern, different.

@Adam_Lassek @fxn When I read the tone of your comments it seems like you’re arguing. When I read the content of your comments it seems like you’re loudly agreeing with each other (but maybe not realizing it because you’re each grabbing at a different part of the elephant.)

I’d like to summarize what I think you’re both saying, so that the “argument tone” doesn’t wind up confusing newbies who might struggle a little more to understand the content.

The “Concern” pattern is another name for using Ruby modules as mixins. There’s nothing special about concerns when they’re done as PORMs.

However, when a module is created using concerning or by extending ActiveSupport::Concern, there’s some special dependency resolution logic that happens that is Rails-specific.

Does this feel like an accurate summary?

3 Likes

There is no argument, Betsy.

This is a technical conversation about a technical topic.

My experience is that it is often difficult, especially on the internet where tone doesn’t convey, to tell the difference between finely grained technical corrections and arguments. See also the Social rules - Recurse Center section on “well, actuallys.”

I’d like us to be extremely careful about this within this forum, because it’s intended as a welcoming space for people to bring things that confuse them. If they feel well-actuallyed, particularly by a core team member, that shuts conversation down and we lose the data about the Rails experience that they would otherwise bring to the table.

My summary was intended as a way to add context to your discussion. If a newbie googles about concerns, then a topic on the official Rails forum called “helping devs understand concerns faster” is going to have a lot of SEO juice. I’d like any newbies reading this to come away with the correct technical impression, and I was afraid that that was getting lost within the back-and-forth.

7 Likes

All that said, I fundamentally agree with @fxn’s point that PORM mixins are a Ruby basic that Rails developers need to learn about to be proficient.

I think the binary between “no help” and “smothering levels of handholding” is a false one though. Maybe we can find some solution in between?

I think the Rust compiler threads a similar needle well. Its errors are extremely helpful in guiding you away from stupid pointer mistakes. However, in order to fix those mistakes a developer will need to learn to understand pointers, the stack, and the heap; and the Rust compiler does not pretend otherwise.

I don’t think an error-oriented approach is the right one here, I think “I don’t understand what this is for” demands something more proactive. I have no idea what, but I’m hoping someone else does.

2 Likes

Yes, concerns are just modules, yet they’re also more than that. They’re also about a style of project organization. So they’re really doing a lot of work, and no wonder it’s confusing! And just like it’s not always obvious that app/models can contain POROs or other non-db backed models, it’s not obvious that app/models/concerns isn’t really where you’d put most concerns.

I’m extracting from DHHs screencast here.

We have an Active Record model that includes a concern, here expressed as just a module, which wraps a PORO for a nicer call API. All namespaced within User:: and app/models/user. This is how the vast majority of models at Basecamp look, for instance.

Note: this is about the organization, not so much the content within, in the real app there’d be more code in these pieces.

# app/models/user.rb
class User < ApplicationRecord
  include Notifiee
end

# app/models/user/notifiee.rb
module User::Notifiee
  # Philosophically a concern, i.e. a role a model can play, but:
  # extend ActiveSupport::Concern is not needed without included/class_methods calls.

  def notifications
    @notifications ||= User::Notifications.new(self)
  end
end

# app/models/user/notifications.rb
class User::Notifications
  attr_reader :user
  delegate :person, to: :user

  def initialize(user)
    @user = user
  end
end

I totally wish we had some more documentation or tooling that could push you in this direction or make it more obvious how this could work — perhaps in guides or generators.

9 Likes