-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Disable Rails/ContentTag #187
Conversation
Interesting find! I'm not sure about this - in particular the "targeting the wrong method" bit 🤔 I recall # To produce <span class="bookmark"></span> before:
content_tag :span, nil, class: 'bookmark'
# After (because we will keep an index of tags that need closing, like <span>, vs those that do not, like <br>)
tag.span class: 'bookmark'
# To produce <div id="post_1"> before:
content_tag :div, post.title, id: dom_id(post)
# After:
tag.div post.title, id: dom_id(post)
# To produce <br> before:
tag :br, nil, true
# After (becomes like span, we know this tag doesn't need closing)
tag.br
# Would be nice to support nesting too, so to produce <div id="header"><span>hello</span></div>, you'd:
tag.div(id: 'header') { |tag| tag.span 'hello' }
I certainly prefer the syntax of The slowness is a little concerning, but I suppose we don't use Rails or Ruby mainly for its speed and ultimately any Rails HTML helper will always be slower than a raw string. I'm not good enough with numbers to tell from the benchmarks what using However, the general approach we've taken so far is to use any syntactic sugar that's available in the framework and only drop down when/if needed (for hotspots that would usually mean going all the way to writing "manual" HTML). So, not really concluding anything here 😅, but while it's true that it's hard to find any mention of what the future holds for |
Yeah, I think the Rubocop cop might have been a bit misleading, and there are multiple levels to look at:
About performance, it's always a bit tricky because we always want to keep things fast and optimized, but it's important to put it in relation to its cost in terms of development comfort. In the linked issue's benchmark, there seems to be an average 1.3ms difference between the fastest and slowest of equivalent calls using different syntaxes ( 100 calls in a single page render are not unrealistic for a page with no cache, but I would expect it to be a lot less when making good use of partial caching. I want to also mention that I feel that we may have been abusing -<%= tag :meta, content: "False", itemprop: "isAccessibleForFree" %>
+<meta content="False" itemprop="isAccessibleForFree"> -<%= content_tag :div, nil, class: "text-center sm:text-left card p-sm", data: { controller: "banner" } do %>
+<div class="text-center sm:text-left card p-sm" data-controller="banner"> Here's another data point: it looks like we currently have 120 of such calls to either In the end, I agree that the performance issue might be a concern, which we may benefit from looking into, but I also think that we should try to steer away from syntaxes that feel less comfortable to use (and may be legacy, although that kinda needs to be reassessed...). If performance is really a concern, would there be an easy way for us to profile calls to I also wonder if it would be worth it starting a conversation on rails/rails regarding whether After all this, I'm not sure anymore whether the Rubocop cop is justified, but I'm not sure either it would make sense to keep so many ways of doing the same thing. 🤔 |
I missed that line in the comments/description. I was scanning for a "Legacy syntax" section in the docs as they added for the old flavor of On that note, what does "legacy syntax" even mean? Is this an official designation or does it just indicate "we like this other way better and maybe someday might deprecate these other ways"? Either way, if there is possibly some performance gain to using Maybe if it does actually become deprecated we can address changing
Isn't "many ways of doing the same thing" kinda part of the core philosophy or Ruby? 😉 😄 |
I was expecting that comment, thank you for not letting me down! 😄 I agree though that the "legacy" state of both |
Happy to have fulfilled the expected trolling function! 😆
I totally agree on consistency in general especially with regards to higher-level patterns and problematic syntax. In this instance, forcing consistency feels a bit low-level and unnecessary to me. However, if the team is in favor of requiring the new tag syntax (despite the possibly minor performance issues), I am fine with that. I just found it a bit annoying to have Rubocop ping me about an already existing usage of |
Hehe, yes I know that feeling! If you're just moving things around, it feels "unfair" to have a bot (or human!) tell you to fix old code that just happens to be inside the block you're moving. 😅 (You're welcome to tell the bot "listen, I'm just moving this" in those cases btw!) As for In that regard, our linter helps not only with consistency, but also in making us aware of alternatives (at any level) that, even if the difference is minor, we sort of accept are "better" (or at least not worth having to think about.) I also agree with @davidstosik that a lot of the time, plain text HTML is perfectly adequate and even preferable! (otherwise we would be writing HAML 😅 ) Therefore, in the context of performance optimization, it would probably make more sense to go from (incidentally, seems to me that if All that is to say that, I guess having talked this through, I'm personally leaning towards keeping the cop? 🤔 |
Great point! I wanted to give it a try, and first thing I did was to write down a benchmark (basically the same that is in the PR @jesseclark linked), and that is when I realized the submitter's benchmark is using So it turns out that the values he showed are not seconds but milliseconds, and that the timings I mentioned in my previous comment must be divided by 1000! I thought my conclusions were a bit surprising... So we're actually talking about a 1.3µs* (microsecond or 0.0000013s) difference on average between a call to In conclusion, I don't think the performance issue is a concern for us. Here's a stand-alone benchmark script: #/usr/bin/env rails runner
SAMPLE_SIZE = 50_000
def helper
ApplicationController.helpers
end
Benchmark.bmbm(22) do |x|
x.report("content_tag") { SAMPLE_SIZE.times { helper.content_tag(:span, "hey") } }
x.report("content_tag with block") { SAMPLE_SIZE.times { helper.content_tag(:span) { "hey" } } }
x.report("tag") { SAMPLE_SIZE.times { helper.tag.span("hey") } }
x.report("tag with block") { SAMPLE_SIZE.times { helper.tag.span { "hey" } } }
end
(* On the PR's submitter's machine. 1.8µs on mine.) I was curious about @balvig's suggestion for optimization by skipping #/usr/bin/env rails runner
SAMPLE_SIZE = 50_000
require "action_view/helpers/tag_helper"
class ActionView::Helpers::TagHelper::TagBuilder
def fast_span(*args, **options, &block)
tag_string(:span, *args, **options, &block)
end
end
helper = ApplicationController.helpers
# check that the method was properly defined
raise unless helper.tag.fast_span("hey") == "<span>hey</span>"
Benchmark.bmbm(22) do |x|
x.report("content_tag(:span") { SAMPLE_SIZE.times { helper.content_tag(:span, "hey") } }
x.report("tag.span") { SAMPLE_SIZE.times { helper.tag.span("hey") } }
x.report("tag.fast_span") { SAMPLE_SIZE.times { helper.tag.fast_span("hey") } }
end
The results are surprising: there is an improvement, but very tiny and not even close to being on par with |
Ok. Thanks for the discussion, everyone. And @davidstosik thanks for the performance investigations! The cop stays and I will close this ticket. 👍🏼 |
Thank you for raising @jesseclark. 🙏 |
What
Disable the Rails/ContentTag cop.
Why
There is an open PR for Rubocop that makes a pretty compelling argument that this cop targets the wrong method.
And you can see in the Rails docs that there is a legacy syntax section for the #tag method but no deprecation or legacy syntax is mentioned for #content_tag.
cc: @cookpad/web-chapter