简体   繁体   中英

ruby/rails: extending or including other modules

I separated my modules so they are easier to read and search like

lib
  features
    - running.rb
    - walking.rb
  features.rb

And they have

# lib/features/running.rb
module Features::Running
  extend ActiveSupport::Concern

  module ClassMethods
    def can_run
      ...
    end
  end
end

and the other is:

# lib/features/walking.rb
module Features::Walking
  extend ActiveSupport::Concern

  module ClassMethods
    def can_walk
      ...
    end
  end
end

And I might have a lot of this someday.

My problem is when I want to add them in a model, I'd need to

# Sample model
class Man < ActiveRecord::Base
  # Include features modules
  include Features::Walking
  include Features::Running

  # Define what man can do
  can_walk
  can_run
end

I was wondering if there is a way to create another module and include all of them. Something like:

# lib/features.rb
module Features
  extend ActiveSupport::Concern

  extend Features::Walking
  extend Features::Running
end

So I'd only need to add

# Sample model
class Man < ActiveRecord::Base
  # Include features modules
  include Features

  # Define what man can do
  can_walk
  can_run
end

Or how should I go about this?

Edit - solution

I got the fix now based on @Chris solution. I got something like this:

module Features
  FEATURES = %w(running walking)

  # include Features::Running
  FEATURES.each do |feature|
    send :include, "Features::#{feature.camelize}".constantize
  end

  module ClassMethods
    # include Features::Running::ClassMethods
    FEATURES.each do |feature|
      send :include, "Features::#{feature.camelize}::ClassMethods".constantize
    end
  end

  def self.included(base)
    base.send :extend, ClassMethods
  end
end

and my other feature modules are now:

# lib/features/running.rb
module Features::Running
  module ClassMethods
    def can_run
      ...
    end
  end
end

# lib/features/walking.rb
module Features::Walking
  module ClassMethods
    def can_walk
      ...
    end
  end
end

Edit - updated solution

module Features
  FEATURES = [Running, Walking]

  # include Features::Running
  FEATURES.each do |feature|
    send :include, feature
  end

  module ClassMethods
    # include Features::Running::ClassMethods
    FEATURES.each do |feature|
      send :include, feature::ClassMethods
    end
  end

  def self.included(base)
    base.send :extend, ClassMethods
  end
end

You can; the issue you're likely running into is that ActiveSupport::Concern automagically extends the methods in ClassMethods onto the target class, rather than combining multiple ClassMethods sub-modules into one. This is unfortunately a little hard to do automatically because of Ruby's cyclical include prevention code, but you can actually do it all manually without AS::Concern.

module Foo
  def foo
  end

  module ClassMethods
    def cfoo
    end
  end
end

module Baz
  def baz
  end

  module ClassMethods
    def cbaz
    end
  end
end

module Bar
  include Baz
  def bar
  end

  module ClassMethods
    include Baz::ClassMethods
    def cbar
    end
  end
end

module Stuff
  include Foo
  include Bar

  module ClassMethods
    include Foo::ClassMethods
    include Bar::ClassMethods
  end
end

class Final
  include Stuff
  extend Stuff::ClassMethods
end

This is a little more verbose, but you can compose pieces as deep as you want, though you have to be careful to always include the parent module and then include ClassMethods onto the corresponding ClassMethods submodule.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM