Epigene
Epigene

Reputation: 3928

How to structure namespaced modules

I am having trouble with Ruby class (constant) lookup within the context of a Rails engine gem.

I have a gem MyGem that is a Rails engine. It defines non-namespaced models that are expected to be overridden by the MainApp that would include the gem and namespaced modules, which are included in gem's and main_app's models to have a DRY way of defining reusable code.

Here is a sample code structure:

Two models

# in /app/models/user.rb        
class User < ActiveRecord::Base
  include MyGem::User::CommonExt
end

# in /app/models/comment.rb        
class Comment < ActiveRecord::Base
  include MyGem::Comment::CommonExt
end

Their two modules

# in /app/models/concerns/my_gem/user/common_ext.rb
module MyGem::User::CommonExt
  def load_comment(id)
    return Comment.find(id)
  end
end

# in /app/models/concerns/my_gem/comment/common_ext.rb
module MyGem::Comment::CommonExt
  def load_user(id)
    return User.find(id)
  end
end

Now, if I call

User.new.load_comment(1)

I get undefined method #find for MyGem::Comment::Module

I think I understand why this is happening - in the context of #load_comment definition, which is namespaced under MyGem, Comment constant lookup returns MyGem::Comment, rather than the more distant ::Comment

I would prefer not to have to prepend every model instance with ::.
Is there a file structure, model/class definition or configuration change I could use to make a call to Comment return the model Comment, not the MyGem::Comment module?

Upvotes: 0

Views: 34

Answers (1)

Midwire
Midwire

Reputation: 1100

I would use inheritance instead of mixin in this case.

So in your gem/engine you could define your common class similar to this:

module MyGem
  module Common
    class Base < ActiveRecord::Base
      # common functionality goes here
      def load(record_type, id)
        record_type.find(id)
      end
    end
  end
end

Then in your main_app code:

class User < MyGem::Common::Base
  ...
end

Now you could do this:

User.new.load(Comment, 1)

This violates the Law of Demeter but hopefully it illustrates the point.

Doing it like this is DRY and has the added benefit that it prevents your gem from having to know about classes which are outside it's own scope.

Upvotes: 0

Related Questions