1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
require 'adamantium'
require 'equalizer'
# A mixin to define a composition
class Concord < Module
include Adamantium::Flat, Equalizer.new(:names)
# The maximum number of objects the hosting class is composed of
MAX_NR_OF_OBJECTS = 3
# Return names
#
# @return [Enumerable<Symbol>]
#
# @api private
#
attr_reader :names
private
# Initialize object
#
# @return [undefined]
#
# @api private
#
def initialize(*names)
if names.length > MAX_NR_OF_OBJECTS
fail "Composition of more than #{MAX_NR_OF_OBJECTS} objects is not allowed"
end
@names, @module = names, Module.new
define_initialize
define_readers
define_equalizer
end
# Hook run when module is included
#
# @return [undefined]
#
# @api private
#
def included(descendant)
descendant.send(:include, @module)
end
# Define equalizer
#
# @return [undefined]
#
# @api private
#
def define_equalizer
@module.send(:include, Equalizer.new(*@names))
end
# Define readers
#
# @return [undefined]
#
# @api private
#
def define_readers
attribute_names = names
@module.class_eval do
attr_reader(*attribute_names)
protected(*attribute_names)
end
end
# Define initialize method
#
# @return [undefined]
#
# @api private
#
# rubocop:disable MethodLength
#
def define_initialize
ivars, size = instance_variable_names, names.size
@module.class_eval do
define_method :initialize do |*args|
args_size = args.size
if args_size != size
fail ArgumentError, "wrong number of arguments (#{args_size} for #{size})"
end
ivars.zip(args) { |ivar, arg| instance_variable_set(ivar, arg) }
end
private :initialize
end
end
# Return instance variable names
#
# @return [String]
#
# @api private
#
def instance_variable_names
names.map { |name| "@#{name}" }
end
# Mixin for public attribute readers
class Public < self
# Hook called when module is included
#
# @param [Class,Module] descendant
#
# @return [undefined]
#
# @api private
#
def included(descendant)
super
@names.each do |name|
descendant.send(:public, name)
end
end
end # Public
end # Concord
|