Question

Suppose we are using Rails or just ActiveSupport::Dependencies module. Now lets look to this code:

animal.rb

class Animal
  CHILD = {
    cat: Cat
  }

  # factory!?
  def self.child(name)
    CHILD[name].new
  end
end

cat.rb

class Cat < Animal
end

dog.rb

class Dog < Animal
end

Somewhere before all the classes are loaded:

Cat.new

That code will result in a B is not a class (TypeError) (activesupport 3.x) or Circular dependency detected while autoloading constant B (activesupport 4.x) because it wasn't created yet, but the name of the class already in a class table.

To solve this problem it's possible to require 'a', then A will require B.

Can give me some better solution to the following issue?

Was it helpful?

Solution

You should avoid circular dependencies if you do not want / can't use require. One way to avoid them is replacing class loading level calls with runtime level ones:

class Animal
  CHILD = {
    cat: 'Cat'
  }

  def self.child(name)
    CHILD[name].constantize.new
  end
end

Indeed self.child can't be called at class level now, or you will be in a circular dependency context again.

OTHER TIPS

For this reason, and to avoid autoloading a ton of other classes at either Rails initialization or when a specific class is loaded, Rails uses a string to specify the class name of an association if required to be specified, e.g.

has_many :published_posts, class_name: 'Post'

because associations have the greatest tendency to have interactions like this.

So, if you have control over what you use and can delay load of a constant until it is needed in Ruby/Rails by using a string to hold the name of the constant until you need to constantize it, it might not be a bad idea. But, like anything, the best solution depends on your requirements.

This may seem wierd if you are coming from another programming language background like Java where historically, at least, developers would avoid using reflection to dynamically instantiate a class from a String because of the overhead (at least in older versions) and the possibility of errors when loading the class later versus catching them early-on at startup. But, it really isn't a bad thing here. And this also doesn't stop you from eager loading as much as you want up-front, because really what it is doing is eliminating the direct dependency caused by constant reference during the load.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top