Home > OS >  Rails map array of word or enum to integer or float value
Rails map array of word or enum to integer or float value

Time:09-08

I'm not sure what this question is even called, but is it possible to have a list in a model via:

array of word

equipment %w[foo bar kimi etc...]
equipcost %i[10 35 85 etc...] 

or enum

enum equipment: { foo: 10,
                  bar: 35,
                 kimi: 83,
                 etc...
               }

Then save multiple in an array i.e.

 t.string "equipment_list", default: [], array: true

{ equipment_list => ["foo", "bar"] } or { equipment_list => [10, 35] }

Then when the object is called via @object.equipment_list, it references the the array of words or enum.

So in the view I say <%= @object.equipment_list %> and have the foo and bar display. Or if I'm in the model I can have a method that adds the values to get a total cost:

def cost
   e = self.equipment_list
   e.value
   e.inject(: )
end

Is there a ruby or rails way of doing this? The array of words way seems wrong and the emun way is only for a single value.

I found this but like they say it is an index not an actual value.

CodePudding user response:

Columns that are serialized as arrays are seldom a good way to go.

What you need is an equipments table, with columns name and cost

This table is associated with another model, I'll call it Store just for an example:

# equipments.rb
class Equipment < ApplicationRecord
  has_and_belongs_to_many :stores
end

# stores.rb
class Store < ApplicationRecord
  has_and_belongs_to_many :equipments

  def equipment_cost
    equipments.pluck(:cost).sum
  end
end

CodePudding user response:

Hardcoding that data into your application is just not a good idea unless you want to change the code every time the buisness logic demands trivial changes or you like being called up at 3 am. Using an array/JSON/HSTORE/Seialized Garbage Varchar column isn't actually a good approach either:

  • No data normalization
  • Bad type support (no Decimal type)
  • No Assocations
  • Violates First Normal Form (1NF)

Instead create a join table where you can store the relation between a store and equipment as well as any additional data that describes the relation between the two buisness entities:

class Equipment < ApplicationRecord
  has_many :store_equipments
end

class Store
  has_many :store_equipments
end

# rails g model store_equipment store:belongs_to equipment:belongs_to cost:decimal
class StoreEquipment < ApplicationRecord
  belongs_to :store
  belongs_to :equipment
end

Naming the table/model after the two things it joins is just a very lazy convention - use a more appropriate name that fits the buisness logic of what that thing actually represents when possible.

The exact implementation here will depend on your buisness logic and you'll have to consider stuff like how you want to handle quantities. If you wanted to get the sum of the cost of the equipment the naive implementation is:

# for a single record
store = Store.find(1)
equipment_cost = @store.store_equipments.sum(:cost)

# fetch it for a group of records in a single query
stores = Store.left_joins(:store_equipments)
               .select(
                 Store.arel_table[Arel.star],
                 StoreEquipment.arel_table[:cost].sum.as('equipment_cost')
               )
               .group(:id)
  • Related