Helping devs understand concerns faster

@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.

6 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.

10 Likes

I think we’d do well to explain concerns better. I’ve been writing code with mixins for so long that I’ve taken it completely for granted that this is a wonderful way to structure separate domain concerns.

But I also agree that concerns do not warrant a generator. A lot of concerns are simply Ruby modules that are mixed in, with no additional stuff added. And then for a few cases, like when your concern includes both class and instance methods, or you want to call class methods on mixin, you can use extend ActiveSupport::Concern.

I’d also just put my two cents here that I’m actually not that big a fan of Concerning. That is adding inline concerns in a class. We don’t use that at Basecamp any more. It was an approach introduced by @bitsweat, so I’ll let him speak to what he thinks about it.

But I went over the way we use concerns at Basecamp at length in that video that was linked above. Here’s another example from HEY:

class Identity < ApplicationRecord
  include Accessor, Authenticable, Boxes, ClearancePreapproval, Clipper, Creator, Eventable,
    Examiner, Filer, Member, PasswordReset, Poster, Reaching, Regional, TopicMerger
end

module Identity::Accessor
  def accessible_topics
    Topic.joins(:accesses).where(accesses: { contact: contacts })
  end

  def accessible_entries
    Entry.joins(:topic).merge(accessible_topics)
  end
end

As you can see, we often put as little as 1-2 methods into a concern that encapsulates that specific concern. The way you reach accessible objects. These objects absolutely could just have been declared in Identity, but add all those concerns up we have in Identity, and you’ll see that Identity would be a big basket of dozens of methods. Sure, all these methods are about Identity, but there’s a grouping below that. That’s what concerns are: A way to group related methods together, while still living under a single class.

You can think of it like this: The class is an H1, the concern is a H2, the public method is an H3, and private methods could play H4/p. It’s a way to structure a large argument. Sit down, and lemme tell you about the Identity class! Let’s start by talking about how the Identity acts as an Accessor, and you should reach what that identity can see as an accessor through the accessible_* methods declared in the Accessor role.

It’s structured writing.

18 Likes

Worth pointing out here is that the stuff we put in app/models/concerns are concerns that apply to multiple classes. An example from HEY is eventable:

module Eventable
  extend ActiveSupport::Concern

  included do
    has_many :events, as: :eventable, dependent: :destroy
...

class Access < ApplicationRecord
  include Eventable

class Clip < ApplicationRecord
  include Eventable

But the majority of our concerns are actually like those presented for the identity: Just a way of breaking up a large argument, like Identity, into smaller ones, like Accessor.

Now I understand that argument from people who say: well, Accessor should just be it’s own class! Yes, it could have been. You could do Accessor.new(identity).accessible_topics. But I find that style awkward. The fact that the identity is playing the role of an accessor is implicit in #accessible_topics.

So that’s another way of putting this: It’s a writing style. Like using subheads to explain subservient ideas within a broader context. You could probably extract all those subheads out, and turn them into little essays of their own. But that’s often just not the right level of extraction. As you saw, the Accessor role starts life as literally just two methods! It doesn’t warrant being turned into it’s own class. It doesn’t have an independent center of gravity.

As you’ll probably gather from all the writing metaphors, this isn’t a technical debate in the sense of “When x, then do y”. It’s a discussion of writing styles. Subtle breaking points. Careful evaluations.

But at the same time, you can absolutely teach style. A Rails Strunk & White would be great :smile:

19 Likes

I think that we can do more work in the guides on the getting started section. Current getting started demo app is ok but it is extremely bare-bones that just shows how easy is to start with Rails. We could expand a little(not too much) on that app and show how concerns can be used and other stuff too.

We could expand that section to introduce:

  • Sessions - login/logout
  • Refactoring with concerns
  • Tests and Fixtures
  • A dash more of Turbolinks and webpack
  • Maybe also add Mailers and background jobs
  • Maybe even deployment

I had a pleasure in my life to coach a few young devs that were new to Rails. I couldn’t accomplish a good introduction to Rails with the guides, we needed to use other paid courses/resources just for a basic introduction. But, if we expand a bit on the getting started demo app I feel that can be the best starting point.

6 Likes

When you said this, it made me think of the Securing Rails Applications guide. Not quite the same but something to get to some inspiration from perhaps.

1 Like

I had no idea they were a thing until I watched those videos.

1 Like

There is a demo app? Cool, I never noticed such a thing existed, when I started out, I always loved to see examples of how things were done.

I think something that often gets missed regarding concerns is that they’re basically just syntactic sugar on top of standard Ruby mixins, and the problem there is that some other popular programming languages have little to no proper support for mixins. (I’m looking at you, JS! But some do, like Protocols in Swift/Objective-C.)

I personally am a massive fan of mixins/concerns, and I think they would be better regarded if the education piece started at a more fundamental level like “Why Ruby Has Mixins and Why That’s Awesome” and compared what they provide developers vs. non-Ruby-esque programming techniques.

6 Likes

Also: in my experience a lot of non-ruby coders are required to work on rails code bases. They will never dig deep into ruby and just make code, that is java-esque and have all the popular patterns. But it will be bad Ruby code. And it will have zero mixins as their primary languages don’t support mixins.

1 Like

I just wanna echo the idea of writing a guide or set of guides to convey the ideas that DHH’s expressed here and in his screencasts. Not just about how to use concerns/mixins on a technical level, but about how to use them philosophically.

The omakase can extend to “tried and true” patterns and pathways that newbies can look to for advice on setting up their new (read: maybe not even first) app. The starting point for a guide could literally be what DHH just posted in terms of content and that would be a 10x improvement over what we have.

5 Likes

There is getting started app example here. So what I’m proposing is to expend this “getting started” section and showcase how other parts(including concerns) can be used in the context of real application.

For example on thing could be how controller concerns could be used on the scoped controllers:

class Posts::CommentsController < ApplicationController
  include PostScoped
  
  ...
end

class Posts::ParticipantsController < ApplicationController
  include PostScoped
  
  ...
end

module PostScoped
  extend ActiveSupport::Concern

  included do
    before_action :set_post
  end

  def set_post
    @post = Post.find(params[:post_id])
  end
end
1 Like

I understand that Concerning might seem to fall in the valley between useless and incorrectly placed.

Let me go off on a tangent and make my case for it: it’s a good temporary, low-overhead tool for chunking of concerns. It’s “good enough for now” instead of “perfect or nothing”.

It allows me to group methods together while I’m in context and express “this all goes with the same chunk” without needing to break out into another file, typing three boilerplate lines of code. It’s communicative.

In the same spirit as the H1-H2- model, I can just markup my H2 here and there and keep writing instead of needing to declare-extend-include. If I reorganize later and learn new things about the future, it’s easily undone without worrying I’ll break something. The code works exactly the same, but what it communicates to humans changes.

I don’t think long term you’d want a component with a bunch of concernings laying around. But taking that first step of chunking while you have the context in mind and the future complexity is unknown is super useful. I can come back another day, when my concern has grown large, and take the time to place it into its own file. Or I could have learned that this didn’t complexify as expected, and remove the concerning boundary. It’s a lean, iterative tool to me.

If I may make a gardening metaphor (I’m propagating pothos’ these days):

Concerning is the water jar I put my cuttings in to grow roots. Maybe it will, then I’ll transfer it to its own pot – or maybe it won’t and a pot wasn’t necessary – in which case I saved the effort.

4 Likes

On the one hand I am also unsure if a Concern / mixin tutorial is needed in the Rails guides. On the other hand, I’ve seen many projects were Concerns have just been used to extract parts of a god object into other files to make rubocop happy. And this is a code smell, which can lead to hard to understand and maintain code. So yes, maybe there is some more education on this needed.

sighh, if only these were in text format instead!

well, clearly then that’s the problem. sounds something like coding “for” the linter instead of coding “with” the linter. i feel like this is a common problem accross programmers and not just something about Concerns, so even with extended education i don’t think that sort of issue would go away.

@msapka i’ve seen this loads of times first hand. maybe exposing mixins very very visibly in rails code at the guides or something would help? i’m not sure

For then Ruby is often Rails, so having it in guide would be very useful

2 Likes