Do Basecamp/Hey use ViewComponents?

For us, the winning combination has been ViewComponents, TailwindCSS, and Hotwire.

Out of curiosity, how do you manage standard forms? Do you define a component for each input like TextInputComponent, DateInputComponent, TextAreaComponent, etc.?

That is a rather bold statement, and I beg to differ. You can, not necessarily will, make your view dependencies unmanageable. You can do so with ViewComponents just as well. There is literally nothing stopping you from making a mess of components, just as with of views/partials. It’s a question of experience, not technology. The rhetorics you’ve used is pretty much like the one that ServiceObject pattern adepts are using to bash Rails in general. Tsk-tsk-tsk.

Approach one takes have little to no effect on eventual mess-iness one ends up with. Whether this is an honest mistake, an ignorance, or intentional disinformation, your statement is misleading, at best. Blatantly …bad, at worst.

I didn’t advocate for ViewComponent. I didn’t say it was a solution to dependency management.

When you combine 2 facts

  1. rails models have insanely wide interface (including all their associations)
  2. views have layouts, partials, subpartials, and allow infinite nesting of them, with ability to call into any model anywhere

Then the only way to figure out your view dependencies must be by looking through the entirety of nested views, partials (also helpers), see what you call where, how you construct it, and write it all down. This is what I mean by unmanageable.

ViewComponents don’t solve it entirely, because you can still have a deep tree of objects all calling into various models.

My advice was to have a single object that represents 100% of what you will be rendering at this endpoint, sans html. All values needed for this view are in one place.

You made a harsh characterization, but didn’t provide any counterargument. How exactly would you keep view dependencies manageable with vanilla templating approach and without gathering all view dependencies in one place?

My apologies, I did indeed missed the part where you were proposing a more monolithic approach. With portrayal being an example implementation, if I understand rightly.

Looking at that portrayal gem of yours I can’t shake the feeling that it’s an, essentially, Yet Another Decorator Pattern with but a few bells and whistles on the side.

You’ve mentioned “dependencies … scattered across many files” as a negative effect of status quo — yet,

  • how exactly does this approach prevents one from scattering the view into many files?

Yes, the data access is performed via one proxy object, but:

  • this leads to increase the overall complexity by adding One More Cog to The Machine (especially in already quite complicated projects with hundreds or thousands of classes),
  • for the questionable benefit of… of what, exactly? I can’t help but fail in finding a noticeable benefit of saying @decorator.person.address.street instead of @person.address.street, really.

One interesting (un)related analogy comes to mind: There are 13 US states in which Tesla can not sell their cars directly, as a manufacturer, because local laws demand that all car sales be made via dealerships and dealerships only. Which results in, roughly, 10% increase in car price for an end customer. Who gets the benefit in all this, one may ask? Why, the proxies, of course — the parasites that make enough money to buy and lobby this state of things for almost a hundred years now. Direct sales are better in everything, and, most importantly, in the end cost. Yet, there are still people who invent 1000 and 1 reasons to justify their existence and their right to make money off you just because they can. And forbid you from getting rid of them, too.

Luckily for us, nobody here is forcing anything on anyone. :slight_smile: But the reasons for your approach seems just as elusive…

Now, despite this looking like mere rant, I’m genuinely interested in how the positive aspects, if any (am still not convinced), overweight the added complexity and, what’s worse, boilerplate code (latter being the same one best Rails is fighting against). After all, if someone went through all the effort to put it all up and together, there must be a good reason for that, innit?

Sure, the reason this becomes different is because you will build an object like this, based on your example:

class MyPage < ApplicationStruct
  keyword :address do
    keyword :street
    keyword :city
  end
end

And you will have to construct it somehow. So you will probably add a constructor like this:

def self.from_user(user)
  new address: Address.new(street: user.address_street, city: user.address_city)
end

In controller you would then write:

@page = MyPage.from_user(current_user)

And in views everything will be done via this object:

<strong>street:</strong> <%= @page.address.street %>

This way you know everything that went into creating the “page”. You see a list of every value it requires (declared as keywords), and you see how a user model turns into those keywords (thanks to the from_user constructor).

Moreover, these objects can be reused for JSON and HTML, if your 2 APIs mostly match. Crucially, these objects shouldn’t expose models, they should directly serve the needs of the front-end. That means only primitive (json-serializable) types (strings, numerics, arrays, POROs with the same types). I wrote an article on how to structure these objects.

When your dependencies are this clear, you can optimize this page. For example, you can cache the entire MyPage object, knowing exactly the basis for cache invalidation (because dependencies are clear). You can collect all data for MyPage with a single SQL query — because you have a single constructor where all data is set. You can construct MyPage in tests and conveniently test it. You can open rails console, run MyPage.from_user(User.last) and see exactly what the payload looks like for any particular user, without the html. In my experience, this has been worth it.

Just my two pfennigs here: this is a compelling example. Really like this idea.

Walter

I feel like the only thing I miss when trying to use partials as templates is being able to yield into multiple sections. I wonder if the Rails team would be more open to baking something like nice_partials into the framework :slight_smile:

1 Like

nice_partials seems really promising, even more if it gets added to the Rails core.

The advantage that I see over ViewComponents is that you don’t have to create an extra file with Ruby code, even for simple things.

The downside is that you can’t have helper functions inside the component and that you don’t have previews.

I noticed that nice_partials doesn’t support collections / arrays. ViewComponent is better for that:

I’m not sure if I got you correctly, but I think this is currently possible if you render a partial as a layout.

render(layout: "path/to/partial", locals: locals, &blk)

Since you pass the block, if you call <%= yield %> from the partial, you will be able to have wrapped erb.

For example, you can make a helper like:

def render_my_partial(**kwargs, &blk)
  render(layout: 'path/to/partial', locals: kwargs, &blk)
end

And then use it like this:

<%= render_my_partial(pass: 'locals') do %>
  <div>some content to be used inside the partial</div>
<% end %>

and if you yield with an argument, you could switch on that argument:

<%= render_my_partial do |some_arg| %>
  <% if some_arg == 'section_a' %>
    <div>content for section_a</div>
  <% else %>
    <div>content for section_b</div>
  <% end %>
<% end %>

Here’s an example of what I meant:

Ah I think I understand. Not sure about ViewComponents, but I believe you can build a similar feature with the layout+block approach, if you yield a “config-like” object on which you can call link or any other methods, then use them.

The wrapping partial would look like this:

<% config = Config.new; yield(config) %>

<%# now you can use stuff that block added to the config %>
<% config.links.each do |link| %>
  <%= link_to "…" %>
<% end %>

and outside this would’ve been exactly as the issue showed

<%= render_my_partial do |config| %>
  <% config.link(…) %>
<% end %>

But I agree it would be nice to have something more elegant built-in for this.

I would like to share the solution that we finally used:

https://answers.abstractbrain.com/using-rails-helpers-x-component-for-rendering-viewcomponents

Basically we use ViewComponents, but with an improved, compact syntax for rendering.

1 Like

Don’t understand what you guys mean.
From what I understand, ERB partials already supported what you have tried to accomplish.

__nav.html.erb

<nav><% render_content.call %></nav>

page.html.erb

<%= render partial: 'nav', locals: {
  render_content: lambda do %>
  <%= link_to "Link 1", page_path %>
  <%= link_to "Link 2", another_path %>
<% end } %>

I used it in lots of projects since, like, five years ago; am I missing something?

1 Like