Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ruby: how to require correctly (to avoid circular dependencies)

today i was facing a strange problem: got a 'missing method' error on a module, but the method was there and the file where the module was defined was required. After some searching i found a circular dependency, where 2 files required each other, and now i assume ruby silently aborts circular requires.


Edit Begin: Example

File 'a.rb':

require './b.rb'

module A
    def self.do_something
        puts 'doing..'
    end
end

File 'b.rb':

require './a.rb'

module B
    def self.calling
        ::A.do_something
    end
end

B.calling

Executing b.rb gives b.rb:5:in 'calling': uninitialized constant A (NameError). The requires have to be there for both files as they are intended to be run on their own from command line (i ommitted that code to keep it short). So the B.calling has to be there. One possible solution is to wrap the requires in if __FILE__ == $0, but that does not seem the right way to go.

Edit End


to avoid these hard-to-find errors (wouldn't it be nicer if the require threw an exception, by the way?), are there some guidelines/rules on how to structure a project and where to require what? For example, if i have

module MainModule
  module SubModule
    module SubSubModule
    end
  end
end

where should i require the submodules? all in the main, or only the sub in the main and the subsub in the sub?

any help would be very nice.

Summary

An explanation why this happens is discussed in forforfs answer and comments.

So far best practice (as pointed out or hinted to by lain) seems to be the following (please correct me if i'm wrong):

  1. put every module or class in the top namespace in a file named after the module/class. in my example this would be 1 file named 'main_module.rb.' if there are submodules or subclasses, create a directory named after the module/class (in my example a directory 'main_module', and put the files for the subclasses/submodules in there (in the example 1 file named 'sub_module.rb'). repeat this for every level of your namespace.
  2. require step-by-step (in the example, the MainModule would require the SubModule, and the Submodule would require the SubSubModule)
  3. separate 'running' code from 'defining' code. in the running code require once your top-level module/class, so because of 2. all your library functionality should now be available, and you can run any defined methods.

thanks to everyone who answered/commented, it helped me a lot!

like image 590
user573335 Avatar asked Nov 08 '11 21:11

user573335


People also ask

How do you avoid circular dependencies?

Avoiding circular dependencies by refactoring The NestJS documentation advises that circular dependencies be avoided where possible. Circular dependencies create tight couplings between the classes or modules involved, which means both classes or modules have to be recompiled every time either of them is changed.

How would you fix a cyclic dependency?

A cyclic dependency is an indication of a design or modeling problem in your software. Although you can construct your object graph by using property injection, you will ignore the root cause and add another problem: property injection causes Temporal Coupling. Instead, the solution is to look at the design closely.

How does Javascript handle circular dependency?

There are a couple of options to get rid of circular dependencies. For a longer chain, A -> B -> C -> D -> A , if one of the references is removed (for instance, the D -> A reference), the cyclic reference pattern is broken, as well. For simpler patterns, such as A -> B -> A , refactoring may be necessary.

What is circular dependency detected angular?

A cyclic dependency exists when a dependency of a service directly or indirectly depends on the service itself. For example, if UserService depends on EmployeeService , which also depends on UserService . Angular will have to instantiate EmployeeService to create UserService , which depends on UserService , itself.


2 Answers

After asking about this on the Ruby mailing list a while back, when I used to have a file in my libraries just for requiring things, I changed to these two rules:

  1. If a file needs code from another in the same library, I use require_relative in the file that needs the code.

  2. If a file needs code from a different library, I use require in the file that needs the code.

As far as I understand it, Ruby requires in the order it is asked to, and so it doesn't matter about circular dependencies.

(Ruby v1.9.2)

In answer to the comment about the example showing circular dependency problems:

actually, the problem with the example isn't that the requires are circular, but that B.calling is called before the requires have completed. If you remove the B.calling from b.rb it works fine. For example, in irb without B.calling in the code file but run afterwards:

$ irb
require '/Volumes/RubyProjects/Test/stackoverflow8057625/b.rb'
=> true
B.calling
doing..
=> nil

like image 62
ian Avatar answered Sep 22 '22 13:09

ian


A couple of basic things that you hopefully already know:

  1. Ruby is interpreted, not compiled, so you can't execute any code that hasn't been seen by the interpreter.

  2. require just inserts the code from the file into that point of the program, in other words, a require at the top of the program will be interpreted before a require at the bottom.

(Note: Edited to account for require statements behavior) So if you were to do: ruby a.rb this is what the ruby interpreter would see and execute:

#load file b.rb <- from require './b.rb' in 'a.rb' file

#load file a.rb <- from require './a.rb' in 'b.rb' file
  #this runs because a.rb has not yet been required

#second attempt to load b.rb but is ignored <- from require './b.rb' in 'a.rb' file

#finish loading the rest of a.rb

module A
  def self.do_something
    puts 'doing..'
  end
end

#finish loading the rest of b.rb

module B
  def self.calling
    ::A.do_something
  end
end
B.calling

#Works because everything is defined 

If instead you ran b first, ruby b.rb, the interpreter would see:

#load file a.rb <- from require './a.rb' in 'b.rb' file

#load file b.rb <- from require './b.rb' in 'a.rb' file
  #this runs because b.rb has not yet been required

#second attempt to load a.rb but is ignored <- from require './a.rb' in 'b.rb' file

#finish loading the rest of b.rb
module B
  def self.calling
    ::A.do_something
  end
end
B.calling #NameError, ::A.do_something hasn't been defined yet.

Hopefully this explains the good answers the others have given you, and if you think about it, why it's hard to answer your last question about where to put require statements. With Ruby, you're requiring files not modules, so where you put the require in your code, depends on how your files are organized.

If you absolutely need to be able to have modules defined and methods execute in random order, then you could implement something like this to collect calls on modules that don't yet exist, and then call them when they pop into being.

module Delay
  @@q = {}  
  def self.call_mod(*args) #args format is method_name, mod_name, *args
    mod_name = args.shift
    method_name = args.shift
    #remaining args are still in args
    mod = Object.const_get(mod_name.to_sym)
    mod.send(method_name.to_sym, *args)
  end

  def self.exec(mod_name, *args)
    begin
      args.unshift(mod_name)
      self.call_mod(*args)
    rescue NameError, NoMethodError
      @@q[mod_name] ||= []
      @@q[mod_name] << args
    end
  end

  def self.included(mod)
    #get queued methods
    q_list = @@q[mod.name.to_sym]
    return unless q_list
    #execute delayed methods now that module exists
    q_list.each do |args|
      self.call_mod(*args)
    end
  end
end 

Be sure to define the Delay module first and then rather than calling B.calling you would use Delay.exec(:B, :calling, any_other_args). So if you have this after the Delay module:

Delay.exec(:B, :calling)   #Module B is not defined

module B
  def self.calling
    ::A.do_something
  end
  include Delay #must be *after* any callable method defs
end

module A
  def self.do_something
    puts 'doing..'
  end
  include Delay #must be *after* any callable method defs
end

Results in:

#=> doing..

Final step is to break the code up into files. One approach could be to have three files

delay.rb   #holds just Delay module
a.rb       #holds the A module and any calls to other modules 
b.rb       #holds the B module and any calls to other modules

As long as you make sure require 'delay' is the first line of the module files (a.rb and b.rb) and Delay included at the end of the module, things should work.

Final Note: This implementation only makes sense if you cannot decouple your definition code from the module execution calls.

like image 36
forforf Avatar answered Sep 20 '22 13:09

forforf