Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding Enumerable mixin at the class-level in Ruby

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.

like image 898
ideasasylum Avatar asked Jul 09 '14 10:07

ideasasylum


1 Answers

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
like image 123
Stefan Avatar answered Sep 27 '22 21:09

Stefan