loading...

Polymorphic Model Resource Finder in Ruby On Rails

dpaluy profile image David Paluy ・1 min read

When having a polymorphic association in Rails model, you want to:

  1. generate URL
  2. find the parent model in the controller

See below our model structure. Let's answer those questions the Rails way.

class Company < ActiveRecord::Base
  has_many :images
end

class Product < ActiveRecord::Base
  has_many :images
end

class Image < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
end

Generating Image URL

RoR provides a simple way to generate polymorphic URLs: polymorphic_path

So polymorphic_path([parent, Image]) will generate:

company_images_path(parent) # /companies/1/images

# or

product_images_path(parent) # /products/1/images

Finding the parent model in the controller

If in Image controller, we need to have parent information in order to scope relevant data, we will use ResourceFinder helper.

class ImagesController < ApplicationController
  include ResourceFinder

  def index
    @resources = parentable.images
    render json: @resources
  end
end

and we will create a app/controllers/concerns/resource_finder.rb helper

module ResourceFinder
  def parentable
    klass, param = parentable_class
    return unless klass

    klass.find(params[param.to_sym])
  end

  private

  def parentable_class
    params.each_key do |name|
      if name.match?(/(.+)_id$/)
        model = name[/([^\/.]*)_id$/, 1]
        return model.classify.safe_constantize, name
      end
      nil
    end
  end
end

The parentable_class read the param and define the class associated with the param. In our case, if we have /products/:product_id/images this method will read the product_id params key and will try to create a class from this model. If successful, it will return the key and the class to be used in our scope.

Happy hacking!

Posted on by:

dpaluy profile

David Paluy

@dpaluy

"Of course, I'm an optimist - I don't see much point in being anything else."

Discussion

markdown guide
 
return unless klass

Is never reached, because constantize raises an error if no constant with the given name exists. If you want to be able to return nil if no constant exists, use safe_constantize ;-)

One cool thing about strings in Ruby: you can easily extract substrings via regexs like this:

model = name[/([^\/.]*)_id$/, 1]