# frozen_string_literal: true require 'active_support/callbacks' require 'eac_ruby_utils/arguments_consumer' require 'ostruct' module EacRubyUtils class CommonConstructor attr_reader :args, :options, :after_set_block class << self def parse_args_options(args) result = ::OpenStruct.new(args: [], options: {}) options_reached = false args.each do |arg| raise "Options reached and there is more arguments (Arguments: #{args})" if options_reached options_reached = parse_arg_options_process_arg(result, arg) end result end private def parse_arg_options_process_arg(result, arg) if arg.is_a?(::Hash) result.options = arg true else result.args << arg false end end end def initialize(*args, &after_set_block) args_processed = self.class.parse_args_options(args) @args = args_processed.args @options = args_processed.options @after_set_block = after_set_block end def args_count (args_count_min..args_count_max) end def args_count_min args.count - default_values.count end def args_count_max args.count end def default_values options[:default] || [] end def setup_class(klass) setup_class_attr_readers(klass) setup_class_attr_writers(klass) setup_class_initialize(klass) klass end def setup_class_attr_readers(klass) klass.send(:attr_reader, *args) klass.send(:public, *args) end def setup_class_attr_writers(klass) klass.send(:attr_writer, *args) klass.send(:private, *args.map { |a| "#{a}=" }) end def setup_class_initialize(klass) common_constructor = self klass.include(::ActiveSupport::Callbacks) klass.define_callbacks :initialize klass.send(:define_method, :initialize) do |*args| Initialize.new(common_constructor, args, self).run super(*SuperArgs.new(common_constructor, args, self).result) end end def super_args options[:super_args] end class Initialize attr_reader :common_constructor, :args, :object def initialize(common_constructor, args, object) @common_constructor = common_constructor @args = args @object = object end def run validate_args_count object.run_callbacks :initialize do object_attributes_set object_after_callback end end private def arg_value(arg_name) arg_index = common_constructor.args.index(arg_name) if arg_index < args.count args[arg_index] else common_constructor.default_values[arg_index - common_constructor.args_count_min] end end def object_after_callback return unless common_constructor.after_set_block object.instance_eval(&common_constructor.after_set_block) end def object_attributes_set common_constructor.args.each do |arg_name| object.send("#{arg_name}=", arg_value(arg_name)) end end def validate_args_count return if common_constructor.args_count.include?(args.count) raise ArgumentError, "#{object.class}.initialize: wrong number of arguments" \ " (given #{args.count}, expected #{common_constructor.args_count})" end end class SuperArgs attr_reader :common_constructor, :args, :object def initialize(common_constructor, args, object) @common_constructor = common_constructor @args = args @object = object end def auto_result r = [] sub_args.each do |name, value| i = super_arg_index(name) r[i] = value if i end r end def result result_from_options || auto_result end def result_from_options return unless common_constructor.super_args object.instance_exec(&common_constructor.super_args) end def sub_args common_constructor.args.each_with_index.map do |name, index| [name, args[index]] end.to_h end def super_arg_index(name) super_method.parameters.each_with_index do |arg, index| return index if arg[1] == name end nil end def super_method object.class.superclass ? object.class.superclass.instance_method(:initialize) : nil end end end end