Adding Enumerable mixin at the class-level in Ruby
ruby, ruby-on-rails
Solution
You can use `include` from within `class << self`:
class Foo
class << self
include Enumerable
end
def self.each
yield 1
yield 2
yield 3
end
end
Foo.map { |x| x * 2 } # => [2, 4, 6]
This pattern is used in Ruby's `Prime` class. Writing `include` to include a module looks cleaner to me, but you can probably use `extend` as well (see Uri Agassi's answer).
There would be a difference if the included module relied on the `included` callback (`Enumerable` doesn't):
module M
def self.included(other)
puts "included in #{other}"
end
end
class Foo
class << self
include M
end
end
#=> "included in #<Class:Foo>"
class Bar
extend M
end
#=> nothing
As noted by Зелёный you could define `each` right within the block: (and without using `self`)
class Foo
class << self
include Enumerable
def each
# ...
end
end
end
Problem
I'm using postgres schemas in my Rails app so there's no clear way to query across all companies (for our own analytics). I'd like to implement each method that iterates over all the companies and switches the postgres schema appropriately. I'd like to be able to call: ``` Company.each do |company| # do something in the context of each company end ``` but I'd also like to get some of the other Enumerable methods like `collect` in this example of getting all managers across all companies: ``` Company.collect do |company| Users.managers end ``` Currently I have this which works well ``` class Company < ActiveRecord::Base # ... def self.each(&block) Company.all.each do |company| if Schemas.include? company.subdomain # this changes to that company's schema so all queries are scoped Apartment::Database.switch company.subdomain yield company if block_given? end end end ``` but how do I get the Enumerable mixin at the class-level not the instance level. i.e., when `include Enumerable` is in the class, Enumerable methods get called like ``` company = Company.new # which might iterate over the contents (users?) in a company company.collect {|u| u} ``` but I want to call ``` # iterate over all companies and collect the managers Company.collect {|c| User.managers} ``` and have it use ``` Company.each ``` I feel like the answer is obvious but my meta-programming foo is weak this morning.