Inc1982
Inc1982

Reputation: 1975

Why is ruby responding that I've already mixed in a module when I haven't included it yet?

I have a class Ryte::Theme. If I call included_modules on the class I get back (abbreviated):

[Ryte::Bundleable::Core, Ryte::Bundleable::Validators, Ryte::Bundleable::Builder, ActiveModel::Validations::HelperMethods, ActiveModel::Validations, ActiveSupport::Callbacks, Ryte::Bundleable]

Ryte::Theme is pulling in nested modules through a single module Ryte::Bundleable. Here are the relevant class and module definitions:

class Ryte::Theme
  include Ryte::Bundleable
end


module Ryte::Bundleable
  extend ActiveSupport::Concern

  included do
    include ActiveModel::Validations
    include Ryte::Bundleable::Builder
    include Ryte::Bundleable::Validators
    include Ryte::Bundleable::Core
  end
end

Given this, why is it I receive the following response:

Ryte::Theme.include?(Ryte::Theme::Validators)
=> true

I have not included this additional module (yet).. This is evident in the included_modules response. Is this related to ActiveSupport Concern? I want to be able to include Ryte::Theme::Validators and have it mixin as well but since it thinks it is already included it does not include it 'again' (as it shouldn't if that were true). As such it gets left behind when I add the include to the class definition, like so:

class Ryte::Theme
  include Ryte::Bundleable
  include Ryte::Theme::Validators # <- Does not load
end

Why is this additional module Ryte::Theme::Validators not mixing in as well?

ADDED

Ok just realized:

1.9.3p194 :005 > Ryte::Bundleable::Validators == Ryte::Theme::Validators
 => true 

Odd.. why is this?

Upvotes: 4

Views: 140

Answers (1)

Chris Salzberg
Chris Salzberg

Reputation: 27374

UPDATE: This is related to ActiveSupport::Concern (see below).

Try the following:

# initialize constants
class A; end                               # class Ryte
module A::B; end                           # module Ryte::Bundleable
module A::B::C; end                        # module Ryte::Bundleable::Validators

module A::B                                # module Ryte::Bundleable
  def self.include(base)
    base.class_eval do
      include A::B::C                      # include Ryte::Bundleable::Validators
    end
  end
end

class D                                    # class Ryte::Theme
  include A::B                             # include Ryte::Bundleable
end

A::B::C == D::C                            #=> true

This happens because of the way the modules and classes are namespaced: when you include A::B::C from inside of A::B, the module name is referenced relative to the module itself, which becomes just C (in your case, just Validators). Thus when you include A::B in some other class D, rather than including a module named A::B::C (i.e. Ryte::Bundleable::Validators), ruby includes a module named just C (i.e. Validators). This is why Ryte::Bundleable::Validators == Ryte::Theme::Validators evaluates to true.

However, with the example above:

D.include?(A::B::C)                       #=> false

So this is where ActiveSupport::Concern kicks in. Redefine the module A::B above as follows:

module A::B                                # module Ryte::Bundleable
  extend ActiveSupport::Concern

  included do
    include A::B::C                      # include Ryte::Bundleable::Validators
  end
end

And you will find that D.include?(A::B::C) now evaluates to true. I'm honestly not sure why this happens, but it must have something to do with the namespacing above.

Upvotes: 6

Related Questions