Search code examples
ruby-on-railsrubyactiverecord

Ruby active_record-acts_as gem issue with joining for child and parent class


class Product < ActiveRecord::Base
    actable as: :as_product
end

class Car < ActiveRecord::Base
    acts_as :product, as: :as_product
end

class Truck < ActiveRecord::Base
    acts_as :product, as: :as_product
end

#Table definition
Product
t.id
t.as_product_id
t.as_product_type

Car
t.id
t.vin

Gemfile

gem 'active_record-acts_as'

ruby 3.2.2
Rails 6.1.7.10

The project was already working fine with acts_as_relation but since the gem has been deprecated and now using active_record-acts_as instead

Using this gem has removed the join which automatically use to happen between Car and Product class

Example: With acts_as_relation the query generated join between car and product with the help of as_product_id and as_product_type column which is already defined in the table

p = Product.last
p.vin #This use to give Vin from Car table

With active_record-acts_as no join queries are coming on console/terminal

p = Product.last
p.vin #Now it throws error saying undefined method `vin' for #<Product>

Note While acts_as_relation was being used the project had the starting migration as below

create_table "products", :as_relation_superclass => true do |t|
end

which now with the new gem active_record-acts_as has been updated by running below migration

change_table  :products, :as_relation_superclass => false do |t|
end

Solution

  • This can be obtained using the specific method of https://github.com/chaadow/active_record-acts_as

    products(dev)> p = Product.last
    => 
    #<Product:0x00007432c503aa18
     id: 1,
     created_at: "2024-12-20 02:57:13.912331000 +0000",
     updated_at: "2024-12-20 02:57:13.912331000 +0000",
     as_product_type: "Car",
     as_product_id: 1>
    products(dev)> p.specific
    => #<Car:0x00007432c5030018 id: 1, created_at: "2024-12-20 02:57:13.910996000 +0000", updated_at: "2024-12-20 02:57:13.910996000 +0000", vin: "Foo">
    products(dev)> p.specific.vin
    => "Foo"
    

    If you want, you could replicate the old gem's behavior with delegate :vin, to: :specific or with the following hack:

      def method_missing(meth, *args, &block)
        return specific&.send(meth) if specific.respond_to? meth
        super
      end
    
    products(dev)> Product.last.vin
      Product Load (0.1ms)  SELECT "products".* FROM "products" ORDER BY "products"."id" DESC LIMIT 1 /*application='Products'*/
      Car Load (0.0ms)  SELECT "cars".* FROM "cars" WHERE "cars"."id" = 1 LIMIT 1 /*application='Products'*/
    => "Foo"