Class: JSONAPI::ResourceLoader

Inherits:
Object
  • Object
show all
Defined in:
lib/json_api/resources/resource_loader.rb

Defined Under Namespace

Classes: MissingResourceClass

Class Method Summary collapse

Class Method Details

.active_storage_blob?(model_class) ⇒ Boolean

Returns:

  • (Boolean)


121
122
123
# File 'lib/json_api/resources/resource_loader.rb', line 121

def self.active_storage_blob?(model_class)
  defined?(::ActiveStorage) && model_class == ::ActiveStorage::Blob
end

.build_resource_class_name(resource_type, namespace) ⇒ Object



76
77
78
79
80
81
# File 'lib/json_api/resources/resource_loader.rb', line 76

def self.build_resource_class_name(resource_type, namespace)
  base = "#{resource_type.singularize.classify}Resource"
  return base unless namespace.present?

  "#{namespace.to_s.camelize}::#{base}"
end

.candidates_for_class(model_class, namespace) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/json_api/resources/resource_loader.rb', line 97

def self.candidates_for_class(model_class, namespace)
  resource_type = model_class.name.demodulize.underscore.pluralize
  candidates = []

  # Namespaced resource matching the model's namespace, e.g. API::V1::Widget → API::V1::WidgetResource
  candidates << build_resource_class_name(resource_type, namespace) if namespace

  # Flat-combined name for namespaced models, e.g. Ticket::User → TicketUserResource
  candidates << "#{model_class.name.gsub("::", "")}Resource" if model_class.name.include?("::")

  # Standard flat resource using demodulized model name, e.g. User → UserResource
  candidates << build_resource_class_name(resource_type, nil)

  candidates
end

.extract_namespace_from_model(model_class) ⇒ Object



129
130
131
132
# File 'lib/json_api/resources/resource_loader.rb', line 129

def self.extract_namespace_from_model(model_class)
  parts = model_class.name.deconstantize
  parts.presence&.underscore
end

.find(resource_type, namespace: nil) ⇒ Object



34
35
36
37
38
39
40
41
42
# File 'lib/json_api/resources/resource_loader.rb', line 34

def self.find(resource_type, namespace: nil)
  key = "#{namespace}|#{resource_type}|#{JSONAPI.configuration.namespace_fallback ? 1 : 0}"
  cached = @find_cache[key]
  return cached if cached

  klass = resolve(find_candidates(resource_type, namespace)) ||
          raise(MissingResourceClass.new(resource_type, namespace:))
  @find_cache[key] = klass
end

.find_candidates(resource_type, namespace) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/json_api/resources/resource_loader.rb', line 44

def self.find_candidates(resource_type, namespace)
  candidates = []

  # Namespaced resource, e.g. "widgets" with namespace "api/v1" → API::V1::WidgetResource
  candidates << build_resource_class_name(resource_type, namespace) if namespace

  # Flat resource, e.g. "widgets" → WidgetResource
  if !namespace || JSONAPI.configuration.namespace_fallback
    candidates << build_resource_class_name(resource_type,
                                            nil,)
  end

  candidates
end

.find_for_model(model_class, namespace: nil) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/json_api/resources/resource_loader.rb', line 59

def self.find_for_model(model_class, namespace: nil)
  return ActiveStorageBlobResource if active_storage_blob?(model_class)

  # Key format "::ModelName" is public-ish: applications pre-populate
  # @model_cache with these keys to pin STI subclass resources.
  key = namespace ? "#{namespace}|::#{model_class.name}" : "::#{model_class.name}"
  cached = @model_cache[key]
  return cached if cached

  effective_namespace = namespace || extract_namespace_from_model(model_class)
  candidates = model_candidates(model_class, effective_namespace)

  klass = resolve(candidates) ||
          raise(MissingResourceClass.new(model_class.name, namespace: effective_namespace))
  @model_cache[key] = klass
end

.model_candidates(model_class, namespace) ⇒ Object

Builds an ordered list of candidate class names for a model, including STI base class candidates as a fallback.



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/json_api/resources/resource_loader.rb', line 85

def self.model_candidates(model_class, namespace)
  candidates = candidates_for_class(model_class, namespace)

  if sti_subclass?(model_class)
    base = model_class.base_class
    base_ns = extract_namespace_from_model(base) || namespace
    candidates.concat(candidates_for_class(base, base_ns))
  end

  candidates
end

.reset_cache!Object



29
30
31
32
# File 'lib/json_api/resources/resource_loader.rb', line 29

def self.reset_cache!
  @find_cache.clear
  @model_cache.clear
end

.resolve(candidates) ⇒ Object



113
114
115
116
117
118
119
# File 'lib/json_api/resources/resource_loader.rb', line 113

def self.resolve(candidates)
  candidates.each do |name|
    klass = name.safe_constantize
    return klass if klass
  end
  nil
end

.sti_subclass?(model_class) ⇒ Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/json_api/resources/resource_loader.rb', line 125

def self.sti_subclass?(model_class)
  model_class.respond_to?(:base_class) && model_class.base_class != model_class
end