ruby-on-railsrails-activerecordinflector

Subbing a class name for a table name in ActiveRecord query


I've got this line:

factory.workers.where.not(confirmed_at:nil).where(job_roles: {heavy_lifting:true, sensitive_area: false}).pluck(:work_capacity)

But the association table job_roles depends on the class name of the factory, and I've got 5 types of factories. So if factory.class.name == "DistributionCenter" then job_roles would actually be distribution_center_roles, or if factory.class.name == "AutomotiveAssemblyPlant" then job_roles would be automotive_assembly_plant_roles etc.

Is there any way to sub the class name in the association table name in the ActiveRecord query code, so that instead of it saying job_roles, it says distribution_center_roles or automotive_assembly_plant_roles (etc), based on the class name of factory?

liiiike

factory.workers.where.not(confirmed_at:nil).where((factory.class.name + "_roles").string_to_tablename_rails_method: {heavy_lifting:true, sensitive_area: false}).pluck(:work_capacity)

or do I have to do:

if factory.is_a?(DistributionCenter)
  factory.workers.where.not(confirmed_at:nil).where(distribution_center_roles: {heavy_lifting:true, sensitive_area: false}).pluck(:work_capacity)
elsif factory.is_a?(AutomotiveAssemblyPlant)
  factory.workers.where.not(confirmed_at:nil).where(automotive_assembly_plant_roles: {heavy_lifting:true, sensitive_area: false}).pluck(:work_capacity)
etc

Solution

  • I would use ActiveModel::Naming and a reflection if you want to do this in a less hacky way:

    module RoleScoped
      def with_roles(**conditions)
         reflect_on_assocation(:"#{model_name.singular}_roles")
           .compute_class
           .where(conditions)
      end
    end
    
    class DistributionCenter < ApplicationModel
      extend RoleScoped
    end
    
    class AutomotiveAssemblyPlant < ApplicationModel
      extend RoleScoped
    end
    
    factory.workers
           .where.not(confirmed_at:nil) 
           .merge(factory.class.with_roles(heavy_lifting:true, sensitive_area: false))
           .pluck(:work_capacity)
    

    This avoids making assumptions about the tablenames. Even better would be to name the assocation the same thing in the two classes so that you don't have to monkey around in the first place.

    class DistributionCenter
      has_many :roles, class_name: 'DistributionCenterRole'
    end
    
    class AutomotiveAssemblyPlant
      has_many :roles, class_name: 'AutomotiveAssemblyPlantRole'
    end
    
    
    factory.workers
           .where.not(confirmed_at:nil) 
           .merge(factory.roles.where(heavy_lifting:true, sensitive_area: false))
           .pluck(:work_capacity)