0
votes

What is the best way to group options when using MongoDB?

I am using Mongoid, when I tried this approach:

<%= field.select :resource_id, 
    grouped_options_for_select(Resource.all.group_by{"resource_type_id"}.map {|k,m|
    [m.first.title, m.first.id] }),:prompt => true %>

It gives me the following error:

undefined method `map' for "5177e6a5359f105f89000001":Moped::BSON::ObjectId

While I am looking for:

<select>
    <optgroup label="RT1">   <!-- RT1 is the name of resource type -->
        <option value="5177e6a5359f105f89000001">Res1</option>
    </optgroup>
</select>

Also, in console the output for Resource.all.group_by{"resource_type_id"} is

=> {"resource_type"=>[#<Resource _id: 5177e6a5359f105f89000001, 
created_at: 2013-04-24 14:05:25 UTC, updated_at: 2013-04-24 14:54:14 UTC,
title: {"en"=>"Res1"}, slug: {"en"=>"res1"}, content: 
{"en"=>"This is the content for First Resource."},
excerpt: {"en"=>"This is the content for First Resource."}, published: true,
resource_type_id: "5177e3ba359f10d345000004">]}

While the expected result is

=> {"RT1"=>[#<Resource _id: 5177e6a5359f105f89000001, 
created_at: 2013-04-24 14:05:25 UTC, updated_at: 2013-04-24 14:54:14 UTC,
title: {"en"=>"Res1"}, slug: {"en"=>"res1"}, content: 
{"en"=>"This is the content for First Resource."},
excerpt: {"en"=>"This is the content for First Resource."}, published: true,
resource_type_id: "5177e3ba359f10d345000004">]}
4

4 Answers

0
votes

Several things are wrong here.

  1. You seem to have confused Enumerable#group_by with a database GROUP BY operator.
  2. Where do you think the name of the Resource Type is coming from? It does not appear to be in Resource.

It looks like the name of the Resource Type is in a different collection and you will have to map the IDs to names separately. This is the NoSQL part of Mongoid: you cannot simply join the Resource and ResourceType tables.

Resource.all.group_by { |r| r.resource_type_id }

will get you started, but the hash keys will be IDs, not names. (This will also read all the resources into memory at once, which in many cases would be a problem, but since you're displaying them all in an options list I'm assuming the list is small enough to be okay.) You'll need to then get a name map and replace the hash keys. Something like:

resource_names = {}
ResourceType.each { |rt| resource_names[rt.id] = rt.name }
resource_groups = Resource.all.group_by { |r| r.resource_type_id }
resource_options = Hash[resource_groups.map { |k, v| [resource_names[k] || k, [v.title["en"], v.id]] }]
0
votes

Here's an example of optgroup:

 @city_group =
                 [
                 ["Wisoncin", [["Lake Geneva", "1"], 
                 ["Elkhart Lake", "2"]]],
                 ["Michigan", [["Harbor Country", "3"], ["Traverse City", "4"]]],
                 ["Indiana", [["Bloomington", "5"], ["Valparaiso", "6"]]],
                 ["Minnesota", [["Twin Cities", 
                 "7"], ["Bloomington", "8"], ["Stillwater", 
                 "9"]]],
                 ["Florida", [["Sanibel & Captiva", "10"]]],
                 ["Illinois", [["Chicago", "11"], 
                 ["Galena", "12"]]],
                 ]

and in your views add this:

<%= select_tag(:brand_id, grouped_options_for_select(@city_group, selected_key = "11", prompt = nil)) %>

Hope it helps! Enjoy!

0
votes

The approach mentioned by Old Pro is great, but little longer than expected. I spent some time with the example given in grouped_collection_select helper docs, and the gist was: "In order to keep it simple and object-oriented way, you must traverse from ResourceType (one) to Resource (many) not the other way around". The other way would be confusing as we would be dealing with custom nested arrays with no relation as opposed to the ORM objects..

Therefore, my desired output can be generated by the following Ruby (single-line) code in ERB:

<%= field.grouped_collection_select :resource_id, ResourceType.order_by([:name,:asc]), 
    :resources, :name, :id, :title,  :prompt => true %>

..where :resources and :name belongs to ResourceType and :id and :title are for Resource's options.

Hope it will help others as well.

-1
votes

I am assuming you want to group the options by resource_type rather than resource_type_id.

f.grouped_collection_select :resource_id, 
                            Resource.all.group_by(:resource_type).to_a, 
                            :last, :first, :id, :name

Explanation:

  • Resource.all.group_by(:resource_type).to_a returns an array of arrays.

    [
      [ "R1", [<Resource _id 51xx0001>, <Resource _id 51xx0002>]],
      [ "R2", [<Resource _id 51xx0003>, <Resource _id 51xx0004>]]
    ]
    
  • The last method call on each row the array returned in step 1 returns an array of Resource objects.

  • The first method call on each row the array returned in step 1 returns the Resource Type name.
  • The id and name method call on each row of the array returned in step 2 returns the id and name of the resource object.