DEV Community

Ryan Palo
Ryan Palo

Posted on

What's the best way to render specific items from a Rails collection?

Hi! I've got a small Rails app where I'm going to have a small number (<200) of rows in a database. When I display them, order is going to important to me (making a menu, so I'd like to be able to control which item goes where).

I imagine that querying the database for each individual item as I come to it is not efficient. So I believe what I probably want to do is get all of them and pass them to the view and have the view decide where they should go.

I'd like to be able to do something like this:

<h2>Summer Drinks</h2>

<%= @cocktails["Margarita"] %>

<!-- Or even better yet: -->

<%= cocktail "Margarita" %>
Enter fullscreen mode Exit fullscreen mode

I know that I could have the controller map over the records and store them in a hash by name, pass that hash to the view, and subsequently write a helper function to provide the syntactical sugar. But, is there an easier, Railsier way to do it? It feels like there should be, but my searching hasn't turned up anything.

If it helps, I've got a unique index on the Name column of the Cocktails table.

Additionally, I don't plan to add/update cocktails (think 1-2 database changes/week). I could lean pretty heavily on caching/precompiling if that's a factor in the solution.

Thank you!

Top comments (14)

Collapse
 
ben profile image
Ben Halpern • Edited

What about...

In the controller:

@cocktails = Cocktail.for_sale.to_a # for_sale scope or whatever, solidified as Array.

Helper:

def cocktail(cocktails, name)
  cocktails.select { |c| c.name == name }.first
end

This way you select from the array each time.

If you really want to make the query maximally efficient, you could use pluck which would return an array of arrays containing the values you need. So...

In the controller:

@cocktails = Cocktail.for_sale.pluck(:name, :description, :price) # @cocktails[0] would look like ["margarita", "Good drink", 9] or whatever.

Helper:

def cocktail(cocktails, name)
  cocktails.select { |c| c[0] == name }.first
end

I might be offbase in how I'm thinking about this, but at least it might help you look up some of these methods for use now and/or future.

Collapse
 
rpalo profile image
Ryan Palo

Ok, that makes sense. Scopes and pluck are definitely very useful features to know about. Is there any specific reason that you're sticking to using an array rather than converting to a hash? Or just because arrays are simpler to get to?

Collapse
 
derekjhopper profile image
Derek Hopper

This doesn't answer your question, but have you thought about storing the position as an integer in the database? Would that be an option? Depending on what you want to do, it might be over-engineering, but it could simplify the view. If you had a position column, you might do something like this:

In the controller:

# Margarita (position == 1), Bourbon (position == 2), Beer (position == 3), and so on
@cocktails = Cocktail.order(:position)

In the view:

<%= render @cocktails %>

<!-- or -->

<% @cocktails.each do |cocktail| %>
  <%= cocktail.name %>
<% end %>

It might be something to take a look into as you think about drink groups.

Collapse
 
rpalo profile image
Ryan Palo

That makes sense! It’s a good solution. I’ll look into it. Thanks!

Collapse
 
glaubersantana profile image
Glauber Santana • Edited

How about construct a class CoktailList that fetch and store the records from database as a hash in a instance variable, and delegate the [] method to that variable?

Just to add a example of implementation:

In cocktail_list.rb

class CocktailList
  def initialize(scope = :all)
    @list = Cocktail.send(scope).index_by(&:name)
  end

  # put any desirable hash method here
  delegate :size, :keys, :values, :each, :[], to: :@list
end

In controller:

@cocktail_list = CocktailList.new # or CocktailList.new(:for_sale)

In view:

<%= @cocktail_list["Margarita"] %>
Collapse
 
rpalo profile image
Ryan Palo

Ooh that would be clean too!

Collapse
 
rhymes profile image
rhymes

Do you mean something like this?

<%= render partial: "cocktail", collection: @cocktails %>

from guides.rubyonrails.org/action_view...

It's not clear to me why you'd want them to be transformed to hashes instead of using the objects queried from the DB

ps. if you use caching, cache objects IDs, not the serialized objects themselves

Collapse
 
rpalo profile image
Ryan Palo

To clarify, I'm thinking of making a hash of "cocktail name", "Cocktail instance" pairs, because I commonly know the name of the drink but not the ID. But that's only if there's a distinct performance benefit to querying all of the records from the DB once and keeping them in a hash in memory. That way, in list view, I can say:

## Drink Group A
- Drink A
- Drink C
- Drink E

## Drink Group B
- Drink D
- Drink B

And spell that ordering out manually. And then, if I want to re-order the groups or the orders of the items next week, I can do that without having to figure out the ID's.

I'm planning on using this particular view basically like a Specials board at a restaurant where the particular items on this view might change from week to week and I would like to have fine-grained control over which ones show up where, and be able to adjust that every so often.

Right now I'm just trying to get something good enough to get up on the internet so I don't mind hard-coding the view this way and changing the code when I want to change the Specials board. I know that later, it would likely be easier to have some sort of table that keeps track of Specials board items and groups so that I could do all of this customization in the browser. But right now, like I said, the I've spent too much time thinking about cool ways I "could" do something without shipping anything and it's making me sad, so I really just want a quick, hard-coded way to achieve the minimum useful feature set right now.

Does that make sense at all?

Collapse
 
rhymes profile image
rhymes

How does one cocktail belong to a group or another?

Rails has a helper method called option_groups_from_collection_for_... but it might not work because there's only one relation?

Anyway, if grouping and ordering can't be done via SQL you can do it in memory as suggested by Ben and then test if it's fast enough. If not, you can cache it server side.

I don't think I fully understood the problem but +1 on shipping something that can be iterated :D

Thread Thread
 
rpalo profile image
Ryan Palo

Meh, that's the part I'm not sure about yet. For example, right now, I might have a cocktail under a heading of "Summer Favorites," but maybe it's so good that in October, I remove the "Summer Favorites" group but move that cocktail to the "House Specials" group instead. Or something. Haven't quite fleshed out the idea yet.

Thread Thread
 
rpalo profile image
Ryan Palo

Actually, now that I type that out loud, it gets me thinking that I should probably just make some sort of Group/Category model and render cocktails grouped by Category. Less hard-coding, reasonably easy to implement. Not sure. I seem to have gotten off track from my original question, but I feel like I've got several good options to go forward with.

Thread Thread
 
rhymes profile image
rhymes

Yep! You can also just have a category field for them if category has no other associated metadata.

Thread Thread
 
rpalo profile image
Ryan Palo

Ooh good point. Hm. OK I have some thinking to do. Thank you for your help!

Collapse
 
zimski profile image
CHADDA Chakib

Hey
I think in your case, theses pages are perfect to be cached, so don't bother yourself with over-engineering the fetching part.

Even the list will change not often so you can mess with your sql server.

You can do one sql request for each product (N+1), it's not an issue because to result will be cached.

This will help you to ship code fast and the cache will make it fast too