Module: ClassSpecHelper::CreateClasses
- Included in:
- ClassSpecHelper
- Defined in:
- lib/class_spec_helper/create_classes.rb
Instance Method Summary collapse
-
#create_class(fully_qualified_class_name, base_class = nil, &block) ⇒ Object
create a new class with the provided name, if the provided name is namespaced, then assume each part of the namespace is a module and create create modules as needed to satistfy the fully qualified name.
Instance Method Details
#create_class(fully_qualified_class_name, base_class = nil, &block) ⇒ Object
create a new class with the provided name, if the provided name is namespaced, then assume each part of the namespace is a module and create create modules as needed to satistfy the fully qualified name
if base_class is provided, then the newly created class will extend it
if block is provided, then the newly created class will execute it via class_eval
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 |
# File 'lib/class_spec_helper/create_classes.rb', line 13 def create_class fully_qualified_class_name, base_class = nil, &block # because we're using eval, we validate the class name to ensure it is not mallicious unless /\A[A-Z][a-zA-Z0-9_]*(::[A-Z][a-zA-Z0-9_]*)*\z/.match?(fully_qualified_class_name) raise ClassNameError, "`#{fully_qualified_class_name}` is not a valid class name" end # ensure it was not already created here if Object.const_defined? fully_qualified_class_name raise ClassAlreadyExistsError, "Class `#{fully_qualified_class_name}` already exists within this application" end # ensure it does not already exist if @classes.key? fully_qualified_class_name.to_sym raise ClassAlreadyDynamicallyCreatedError, "Class `#{fully_qualified_class_name}` was already dynamically created with this helper" end # We prepend "::" to the class name to ensure the class is created at the top most scope class_name_parts = fully_qualified_class_name.to_s.split("::") class_name = class_name_parts.pop module_names = class_name_parts eval_code_lines = [] # is this class nested within a namespace? is_namespaced = module_names.any? if is_namespaced first_name = module_names.shift # keep track of the namespace namespace = "::#{first_name}" # does this exist, and is it a class is_class = Module.const_defined?(namespace) && Module.const_get(namespace).is_a?(Class) # first module is always prepended by a "::" to ensure it is at the top most level eval_code_lines << "#{is_class ? "class" : "module"} ::#{first_name}" # each remaining module name is just nested within this top most module module_names.each do |module_name| # keep building the namespace we we go namespace = "#{namespace}::#{module_name}" # does this exist, and is it a class is_class = is_class = Module.const_defined?(namespace) && Module.const_get(namespace).is_a?(Class) # add the next line eval_code_lines << "#{is_class ? "class" : "module"} #{module_name}" end end # the class definition eval_code_lines << "class #{is_namespaced ? nil : "::"}#{class_name} #{base_class && "< #{base_class.name}"}" # add the expected number of "ends" to close the class and any nested modules end_count = eval_code_lines.count end_count.times do eval_code_lines << "end" end eval_code = eval_code_lines.join("\n") # we use `eval` because we want to create a class which immediately has # the expected name, unlike an anonymous class which is initially created # without the expected name and only receives the expected name once assigned # to a constant. eval eval_code # standard:disable Security/Eval # get the new class from the constant klass = Object.const_get fully_qualified_class_name # remember the full class name with namespace, so we can remove it again later @classes[fully_qualified_class_name.to_sym] = klass # finish building the class klass.class_eval(&block) if block # If we are using this in a test suite and the same class names are being used # between each test, then after creation has proven to be a good time to run the # garbage collector manually. It is very likely that all references to the old # class are gone at this point. # # We do this because it is possibe that this is being used within a test suite # for an application which makes use of `ObjectSpace`, and deleted classes will # still be available in `ObjectSpace` until the garbage collector runs. ObjectSpace.garbage_collect end |