4
\$\begingroup\$

Using Rails 6. Here's a piece that I wrote just to display number of stars. Obviously I am disgusted by my own code. How would you refactor?

# application_helper.rb
module ApplicationHelper
  def show_star_rating(rating)
    zero_star_icon_name = "star"
    full_star_icon_name = "star_fill"
    half_star_icon_name = "star_lefthalf_fill"
    total_stars = []
    round_by_half = (rating * 2).round / 2.0
    (round_by_half.to_i).times { total_stars << full_star_icon_name }

    if round_by_half - round_by_half.to_i == 0.5
      total_stars << half_star_icon_name
    end

    if total_stars.size != 5
      (5 - total_stars.size).times { total_stars << zero_star_icon_name }
    end

    total_stars
  end
end

# show.html.erb
<% show_star_rating(agent_review.rating).each do |star| %>
  <i class="f7-icons"><%= star %></i>
<% end %>
\$\endgroup\$
6
\$\begingroup\$

You can make use of the Array.new, passing in the maximum number of stars you want to show, and defaulting all the stars to empty. Then, you can fill in the number of full stars you need. Then, finally, thanks to Numeric's divmod returning either 0 or 1 for the number of half stars you need, you make one more pass and fill in the number of half stars you need:

module StarHelper
  EMPTY_STAR_ICON = 'star'.freeze
  FULL_STAR_ICON = 'star_fill'.freeze
  HALF_STAR_ICON = 'star_lefthalf_fill'.freeze

  def full_and_half_star_count(rating)
    (rating * 2).round.divmod(2)
  end

  def stars(rating, max_stars: 5)
    full_stars, half_stars = full_and_half_star_count(rating)

    Array.new(max_stars, EMPTY_STAR_ICON).
      fill(FULL_STAR_ICON, 0, full_stars).
      fill(HALF_STAR_ICON, full_stars, half_stars)
  end
end
\$\endgroup\$
4
  • \$\begingroup\$ Same as my answer on the StackOverflow question, but it seems more on topic here than over there, so adding it over on this side \$\endgroup\$ Dec 25 '19 at 19:01
  • \$\begingroup\$ I like the fill approach. \$\endgroup\$ Dec 25 '19 at 20:45
  • \$\begingroup\$ Welcome to Code Review, you answer is a great first answer. Thank you for taking the time to port your answer across. \$\endgroup\$
    – Peilonrayz
    Dec 25 '19 at 22:56
  • \$\begingroup\$ Thanks. I'd move the full_and_half_star_count private too. \$\endgroup\$
    – Victor
    Dec 26 '19 at 13:30
1
\$\begingroup\$

I doubt if all those logic is meant to be in the ApplicationHelper. You could create a new helper file and move that logic.

As you have three constant values declared within the method body, you can move them outside as constants, and modify the value of total_stars depending on the conditions you have:

ZERO_STAR_ICON_NAME = "star"
FULL_STAR_ICON_NAME = "star_fill"
HALF_STAR_ICON_NAME = "star_lefthalf_fill"

def show_star_rating(rating)
  round_by_half = (rating * 2).round / 2.0
  total_stars = Array.new(round_by_half, FULL_STAR_ICON_NAME)
  total_stars += [HALF_STAR_ICON_NAME] if round_by_half - round_by_half.to_i == 0.5
  total_stars += Array.new(5 - total_stars.size, ZERO_STAR_ICON_NAME) unless total_stars.size == 5
end
  • You can use unless whenever you have a != condition to make the statement more clear.
  • You can use Array.new to create a new array and sum (+=) it to the current value of total_stars.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.