Include modules in Ruby classes with parameters
Tags: ruby, programming, tutorial
Published: 2022-05-14 12:58:14

## The problem:
We have 2 classes with different behaviors and appointments. But some methods have the same code:

```ruby
class User
  def greeting
    puts "Hello!"
  end
end

class AdminUser
  def greeting
    puts "Hello!"
  end
end

user = User.new
user.greeting # => "Hello!"

admin = AdminUser.new
admin.greeting # => "Hello!"
```

Ruby is a single inheritance language and if classes `User` and `AdminUser` already have different base classes it is impossible to extend from the second one. Anyway, if these classes don't have a base class, it is a bad way to create a base class for both of them special for inheriting the `greeting` method.

## WORST solution:
```ruby
class Base
  def greeting
    puts "Hello!"
  end
end

class User < Base
end

class AdminUser < Base
end
```

## Basic solution:
Modules in ruby are a way to get around single inheritance restrictions:
```ruby
module Greetable
  def greeting
    puts "Hello!"
  end
end

class User
  include Greetable
end

class AdminUser
  include Greetable
end
```

## Complicate the task:
Our app is growing every day. I remind classes `User` and `AdminUser` have different behavior and appointment. We have in each class a person name, but in `User` it is `full_name` and in `AdminUser` it is just `nick`:
```ruby
class User
  include Greetable

  attr_reader :full_name

  def initialize(full_name)
    @full_name = full_name
  end
end

class AdminUser
  include Greetable

  attr_reader :nick

  def initialize(nick)
    @nick = nick
  end
end
```
And we decide to add the nick or the full name to the `greeting` method. In this architecture it is impossible, but we can create some third method like `name` and override it in each class:
```ruby
module Greetable
  def greeting
    puts "Hello, #{name}!"
  end

  def name
    raise NotImplementedError, "not implemented method name"
  end
end

class User
  include Greetable

  attr_reader :full_name

  def initialize(full_name)
    @full_name = full_name
  end

  def name
    full_name
  end
end

class AdminUser
  include Greetable

  attr_reader :nick

  def initialize(nick)
    @nick = nick
  end

  def name
    nick
  end
end
```
It does not look like a good variant, because if we have more than 2 classes we need to implement some hidden method in each one, and need to remember about method `name` whenever we include it. Also if for include some behavior we need to implement some other things in our main class it is a big pain and doesn't make our developer life easy.

## Solution:
Create method `[]` in module `Greetable` with one parameter `name_attribute` which returns a new module with method `greeting` who is using our parameter for puts message:
```ruby
module Greetable
  def self.[](name_attribute)
    Module.new do
      define_method :greeting do
        puts "Hello, #{public_send(name_attribute)}!"
      end
    end
  end
end

class User
  include Greetable[:full_name]

  attr_reader :full_name

  def initialize(full_name)
    @full_name = full_name
  end
end

class AdminUser
  include Greetable[:nick]

  attr_reader :nick

  def initialize(nick)
    @nick = nick
  end
end

User.new("Bob").greeting # => "Hello, Bob!"
AdminUser.new("root").greeting # => "Hello, root!"
```

## Notes:
It is important to use `define_method` in the module instead of just the usual `def method_name` because we need need to use closure for getting the `name_attribute` value. In the case when we use `def method_name` closure is not working inside the `def` code because it has independent scope.

Have a nice coding, guys!