Module: Drawers::DependencyExtensions

Defined in:
lib/drawers/active_support/dependency_extensions.rb

Constant Summary collapse

RESOURCE_SUFFIX_NAMES =
%w(
  Controller
  Forms
  Serializer
  Operations
  Presenters
  Policy
  Policies
  Services
).freeze
ERROR_CIRCULAR_DEPENDENCY =
'Circular dependency detected while autoloading constant'
RESOURCE_SUFFIXES =

Join all the suffix names together with an “OR” operator

/(#{RESOURCE_SUFFIX_NAMES.join('|')})/
QUALIFIED_NAME_SPLIT =

split on any of the resource suffixes OR the ruby namespace seperator

/::|#{RESOURCE_SUFFIXES}/

Instance Method Summary collapse

Instance Method Details

#load_from_path(file_path, qualified_name, from_mod, const_name) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/drawers/active_support/dependency_extensions.rb', line 23

def load_from_path(file_path, qualified_name, from_mod, const_name)
  expanded = File.expand_path(file_path)
  expanded.sub!(/\.rb\z/, '')

  raise "#{ERROR_CIRCULAR_DEPENDENCY} #{qualified_name}" if loading.include?(expanded)

  require_or_load(expanded, qualified_name)
  unless from_mod.const_defined?(const_name, false)
    raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it"
  end

  from_mod.const_get(const_name)
end

#load_missing_constant(from_mod, const_name) ⇒ Object

Load the constant named const_name which is missing from from_mod. If it is not possible to load the constant into from_mod, try its parent module using const_missing.



138
139
140
141
142
143
# File 'lib/drawers/active_support/dependency_extensions.rb', line 138

def load_missing_constant(from_mod, const_name)
  # always default to the actual implementation
  super
rescue LoadError, NameError => e
  load_missing_constant_error(from_mod, const_name, e)
end

#load_missing_constant_error(from_mod, const_name, e) ⇒ Object

the heavy lifting of Drawers is just adding some additional pathfinding / constat lookup logic when the default (super) can’t find what needs to be found

Parameters:

  • from_mod (Class)
    • parent module / class that const_name may be a part of

  • const_name (Symbol)
    • potential constant to lookup under from_mod

  • e (Exception)
    • exception from previous error



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/drawers/active_support/dependency_extensions.rb', line 152

def load_missing_constant_error(from_mod, const_name, e)
  # examples
  # - Api::PostsController
  # - PostsController
  qualified_name = qualified_name_for(from_mod, const_name)
  file_path = resource_path_from_qualified_name(qualified_name)

  begin
    return load_from_path(file_path, qualified_name, from_mod, const_name) if file_path
  rescue LoadError, NameError => e
    # Recurse!
    # not found, check the parent
    at_the_top = from_mod.parent == from_mod
    return load_missing_constant_error(from_mod.parent, const_name, e) unless at_the_top
    raise e
  end

  name_error = NameError.new(e.message)
  name_error.set_backtrace(caller.reject { |l| l.starts_with? __FILE__ })
  raise name_error
end

#path_for_first_file_in(path) ⇒ Object



122
123
124
125
126
127
128
129
# File 'lib/drawers/active_support/dependency_extensions.rb', line 122

def path_for_first_file_in(path)
  return if path.blank?

  path_in_app = "#{Rails.root}/app/resources/#{path.pluralize}"
  return unless File.directory?(path_in_app)

  Dir.glob("#{path_in_app}/*.rb").first
end

#path_options_for_qualified_name(qualified_name) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/drawers/active_support/dependency_extensions.rb', line 100

def path_options_for_qualified_name(qualified_name)
  namespace,
  resource_name,
  resource_type, named_resource_type,
  class_path = ResourceParts.from_name(qualified_name)

  # build all the possible places that this file could be
  [
    # api/v2/posts/operations/update
    to_path(namespace, resource_name, resource_type, class_path),

    # api/v2/posts/post_operations/update
    to_path(namespace, resource_name, named_resource_type, class_path),

    # api/v2/posts/posts_controller
    to_path(namespace, resource_name, named_resource_type),

    # api/v2/posts/controller
    to_path(namespace, resource_name, resource_type)
  ]
end

#resource_path_from_qualified_name(qualified_name) ⇒ Object

Note:

The Lookup Rules:

  • all resources are plural

  • file_names can either be named after the type or traditional ruby/rails nameing i.e.: posts_controller.rb vs controller.rb

  • regular namespacing still applies. i.e: Api::V2::CategoriesController should be in

    api/v2/categories/controller.rb
    
Note:

The Pattern:

  • namespace_a - api

    • namespace_b - v2

      • resource_name (plural) - posts

        • file_type.rb - controller.rb (or posts_controller.rb)

          - operations.rb (or post_operations.rb)
          
        • folder_type - operations/ (or post_operations/)

          • related namespaced classes - create.rb

A look for the possible places that various qualified names could be

All examples assume default resource directory (“resources”) and show the order of lookup

Examples:

Api::PostsController

Possible Locations
 - api/posts/controller.rb
 - api/posts/posts_controller.rb

Api::PostSerializer

Possible Locations
 - api/posts/serializer.rb
 - api/posts/post_serializer.rb

Api::PostOperations::Create

Possible Locations
 - api/posts/operations/create.rb
 - api/posts/post_operations/create.rb

Api::V2::CategoriesController

Possible Locations
 - api/v2/categories/controller.rb
 - api/v2/categories/categories_controller.rb

Parameters:

  • qualified_name (String)

    fully qualified class/module name to find the file location for



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/drawers/active_support/dependency_extensions.rb', line 80

def resource_path_from_qualified_name(qualified_name)
  path_options = path_options_for_qualified_name(qualified_name)

  file_path = ''
  path_options.uniq.each do |path_option|
    file_path = search_for_file(path_option)

    break if file_path.present?
  end

  return file_path if file_path

  # Note that sometimes, the resource_type path may only be defined in a
  # resource type folder
  # So, look for the first file within the resource type folder
  # because of ruby namespacing conventions if there is a file in the folder,
  # it MUST define the namespace
  path_for_first_file_in(path_options.last) || path_for_first_file_in(path_options[-2])
end

#to_path(*args) ⇒ Object



131
132
133
# File 'lib/drawers/active_support/dependency_extensions.rb', line 131

def to_path(*args)
  args.flatten.reject(&:blank?).map(&:underscore).join('/')
end