Home > database >  How can I optimize a model that has one of each enum type of another model?
How can I optimize a model that has one of each enum type of another model?

Time:12-19

I have Bouquet and Flower objects:

class Bouquet < ApplicationRecord
  has_many :flowers
end
class Flower < ApplicationRecord
  belongs_to :bouquet

  enum color: {
    blue: 0,
    red: 1,
    yellow: 2,
    white: 3
  }
end

where a Bouquet will always have exactly one blue flower, one red flower, one yellow flower, and one white flower.

Suppose I have a page where I want to list all bouquets and their flowers in order, how can I query the flowers to minimize page load time?

In view (note that the white flower shows different info than the rest):

<% Bouquet.some_scope.each do |bouquet| %>
  <ul>
    <li><%= bouquet.name %></li>
    <li><%= bouquet.blue_flower.name %></li>
    <li><%= bouquet.red_flower.name %></li>
    <li><%= bouquet.yellow_flower.name %></li>
    <li><%= bouquet.white_flower.name_and_type %></li>
  </ul>
<% end %>
  1. I've tried setting up the Bouquet model as follows:
class Bouquet < ApplicationRecord
  has_many :flowers

  def blue_flower
    flowers.find_by(color: :blue)
  end

  def red_flower
    flowers.find_by(color: :red)
  end

  # etc...

end

Then in the view: <% Bouquet.some_scope.includes(:flowers).each do |bouquet| %>

But all the find_bys ignore the includes and query the database for each flower.

  1. Since the flowers (should) always be created in order, I've also tried
class Bouquet < ApplicationRecord
  has_many :flowers

  def blue_flower
    flowers.first
  end

  def red_flower
    flowers.second
  end

  # etc...

end

which works, but is it the best way of handling this?

CodePudding user response:

Since the flowers (should) always be created in order, I've also tried

SQL has no inherent order. If you want an order, you must provide it. You could flowers.order(:color) and then select off the list assuming that #1 is blue, #2 is red, etc... but that's fragile. What if you add more colors?


Since there's only four flowers, turn them into a Hash keyed on the color.

class Bouquet < ApplicationRecord
  has_many :flowers

  def flower_by_color(color)
    flowers_by_color[color.to_s]
  end

  private def flowers_by_color
    @flowers_by_color ||= flowers.index_by { |flower| f.color }
  end
end

Rather than hard coding the colors, use Flower.colors to loop through all the colors. Then if you add more Flower colors it will still work.

<% Bouquet.some_scope.includes(:flowers).each do |bouquet| -%>
  <ul>
    <li><%= bouquet.name %></li>
    <% Flower.colors.sort.each do |color| -%>
    <li><%= bouquet.flower_by_color(color).name %></li>
    <% end -%>
  </ul>
<% end -%>
  • Related