Module: PunditExtraExtra::ResourceAutoload

Extended by:
ActiveSupport::Concern
Defined in:
lib/pundit_extraextra/resource_autoload.rb

Defined Under Namespace

Modules: ClassMethods

Instance Method Summary collapse

Instance Method Details

#authorize_resource(resource_name = nil, options = {}) ⇒ Object



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/pundit_extraextra/resource_autoload.rb', line 264

def authorize_resource(resource_name = nil, options = {})
  resource_name = (resource_name || controller_name.singularize).to_s
  instance_name = (options[:instance_name] || resource_name).to_s
  resource = instance_variable_get("@#{instance_name}") || resource_name.classify.constantize

  # Determine if this is a parent resource by checking if it was listed as a `through` resource
  is_parent_resource = self.class.resource_options.any? do |opt|
    opt[:options][:through] && Array(opt[:options][:through]).include?(resource_name.to_sym)
  end

  action = is_parent_resource ? :show : params[:action].to_sym
  if resource_name != controller_name.singularize
    action = :show
  end

  if resource.is_a?(Class)
    authorize resource, "#{params[:action].to_sym}?"
  else
    authorize resource, "#{action}?"
  end
end

#create_new_resource(resource_name, action) ⇒ Object



154
155
156
157
158
# File 'lib/pundit_extraextra/resource_autoload.rb', line 154

def create_new_resource(resource_name, action)
  new_resource = resource_name.classify.constantize.new
  new_resource.attributes = resource_attributes(new_resource, action)
  new_resource
end

#find_parent_instance(parents) ⇒ Object



124
125
126
127
128
129
130
131
132
133
# File 'lib/pundit_extraextra/resource_autoload.rb', line 124

def find_parent_instance(parents)
  Array(parents).each do |parent|
    parent_resource_name = parent.to_s.singularize
    parent_instance = instance_variable_get("@#{parent_resource_name}")

    return parent_instance if parent_instance
  end

  nil
end

#find_resource_by_id(current_instance, resource_name, resource_id, find_by_attribute) ⇒ Object



150
151
152
# File 'lib/pundit_extraextra/resource_autoload.rb', line 150

def find_resource_by_id(current_instance, resource_name, resource_id, find_by_attribute)
  current_instance.public_send(resource_name.pluralize).find_by(find_by_attribute => resource_id)
end

#has_permitted_attributes?(resource, action) ⇒ Boolean

Returns:

  • (Boolean)


338
339
340
341
342
343
# File 'lib/pundit_extraextra/resource_autoload.rb', line 338

def has_permitted_attributes?(resource, action)
  return true if policy(resource).respond_to? :"permitted_attributes_for_#{action}"
  return true if policy(resource).respond_to? :permitted_attributes

  false
end

#load_direct_resource(scope, action, resource_id, options = {}) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/pundit_extraextra/resource_autoload.rb', line 214

def load_direct_resource(scope, action, resource_id, options = {})
  # Determine the attribute to find by and the parameter to use for the ID
  # if it isn't specified we assume we're finding by the 'id' column
  find_by_attribute = options[:find_by] || :id

  if action == 'create'
    if resource_id
      resource = scope.find_by(find_by_attribute => resource_id) # Use the custom find_by attribute
    else
      new_resource = scope.new
      new_resource.attributes = resource_attributes(new_resource, action) if new_resource.respond_to?(:attributes=)
      resource = new_resource
    end
  elsif action == 'update'
    resource = scope.find_by(find_by_attribute => resource_id) # Use the custom find_by attribute
    unless resource.nil?
      authorize resource, "#{action}?"
      resource.attributes = resource_attributes(resource, action)
      resource = resource
    else
      resource = nil
    end
  elsif action == 'index'
    resource = policy_scope(scope) # Treat as collection for index
  elsif resource_id
    resource = scope.find_by(find_by_attribute => resource_id) # Use the custom find_by attribute
  else
    resource = policy_scope(scope) # Treat as collection for non-standard actions
  end

  resource
end

#load_index_resource(current_instance, resource_name) ⇒ Object



145
146
147
148
# File 'lib/pundit_extraextra/resource_autoload.rb', line 145

def load_index_resource(current_instance, resource_name)
  resource = current_instance.public_send(resource_name.pluralize)
  policy_scope(resource)
end

#load_nested_resource(parent_instances, resource_name, current_instance) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/pundit_extraextra/resource_autoload.rb', line 172

def load_nested_resource(parent_instances, resource_name, current_instance)
  if parent_instances.size > 1
    query = parent_instances.inject({}) do |hash, parent_instance|
      association_name = parent_instance.class.name.underscore.to_sym
      hash.merge!(association_name => parent_instance)
    end
    resource_name.classify.constantize.find_by(query)
  else
    current_instance.public_send(resource_name.pluralize)
    policy_scope(resource_name.classify.constantize)
  end
end

#load_parent_resources(parents) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/pundit_extraextra/resource_autoload.rb', line 247

def load_parent_resources(parents)
  Array(parents).each do |parent|
    parent_resource_name = parent.to_s.singularize
    parent_id = params["#{parent_resource_name}_id"]
    parent_instance = instance_variable_get("@#{parent_resource_name}")

    unless parent_instance
      parent_scope = parent_resource_name.classify.constantize
      parent_instance = parent_scope.find(parent_id)
      instance_variable_set("@#{parent_resource_name}", parent_instance)
      authorize parent_instance, :show?
    end

    parent_instance
  end
end

#load_resource(resource_name = nil, options = {}) ⇒ Object

Raises:

  • (ActiveRecord::RecordNotFound)


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
# File 'lib/pundit_extraextra/resource_autoload.rb', line 68

def load_resource(resource_name = nil, options = {})
  resource_name = (resource_name || controller_name.singularize).to_s
  instance_name = options[:instance_name] || resource_name
  scope = resource_name.classify.constantize
  action = params[:action]
  varname = instance_name

  # Use id_param option if provided, otherwise fallback to default pattern
  resource_id_param = options[:id_param] || "#{resource_name}_id"
  resource_id = params[resource_id_param] || params[:id]

  if resource_name != controller_name.singularize
    # If the resource being loaded isn't the primary resource for the controller
    # we assume we are loading a single instance of it

    if options[:through]
      # If there's a through option, find the parent instance
      current_instance = find_parent_instance(options[:through])

      if current_instance
        if options[:singleton]
          # If the relationship is has_one, we load the single associated instance
          resource = current_instance.public_send(resource_name)
        else
          # Otherwise, we find by resource_id or simply the first matching resource
          resource = resource_id ? current_instance.public_send(resource_name.pluralize).find(resource_id) : current_instance.public_send(resource_name.pluralize).first
        end
      else
        resource = nil
      end
    else
      # Load the resource directly if no `through` option or if `resource_id_param` is provided
      resource = scope.find(resource_id)
    end

    raise ActiveRecord::RecordNotFound, "No valid parent instance found through #{options[:through].join(', ')}" if resource == nil

    # Authorize the loaded resource for the 'show' action
    authorize resource, "show?"
  else
    resource = if options[:through]
                 load_through_resource(options[:through], resource_name, resource_id, action, options)
               else
                 load_direct_resource(scope, action, resource_id, options)
               end
  end

  raise ActiveRecord::RecordNotFound, "Couldn't find #{resource_name.to_s.capitalize} with #{resource_id_param} == #{resource_id}"if resource.nil?

  if resource.is_a?(ActiveRecord::Relation) || resource.is_a?(Array)
    varname = varname.to_s.pluralize
  end

  instance_variable_set("@#{varname}", resource)
end

#load_singleton_resource(current_instance, resource_name, action, options) ⇒ Object



135
136
137
138
139
140
141
142
143
# File 'lib/pundit_extraextra/resource_autoload.rb', line 135

def load_singleton_resource(current_instance, resource_name, action, options)
  if action == 'create'
    new_resource = resource_name.classify.constantize.new
    new_resource.attributes = resource_attributes(new_resource, action) if new_resource.respond_to?(:attributes=)
    new_resource
  else
    current_instance.public_send(resource_name)
  end
end

#load_through_resource(parents, resource_name, resource_id, action, options) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/pundit_extraextra/resource_autoload.rb', line 185

def load_through_resource(parents, resource_name, resource_id, action, options)
  parent_instances = Array(parents).map do |parent|
    instance_variable_get("@#{parent.to_s.singularize}")
  end.compact

  if parent_instances.empty?
    raise ActiveRecord::RecordNotFound, "No parent instance found for #{resource_name}"
  end

  current_instance = parent_instances.first
  find_by_attribute = options[:find_by] || :id

  resource = if options[:singleton]
               load_singleton_resource(current_instance, resource_name, action, options)
             elsif action == 'index' && (!resource_id && !options[:singleton])
               load_index_resource(current_instance, resource_name)
             elsif resource_id
               find_resource_by_id(current_instance, resource_name, resource_id, find_by_attribute)
             elsif action == 'create'
               create_new_resource(resource_name, action)
             elsif action == 'update'
               update_resource(current_instance, resource_name, resource_id, find_by_attribute, action)
             else
               load_index_resource(current_instance, resource_name)
             end

  resource
end

#process_resource_callbacksObject



47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/pundit_extraextra/resource_autoload.rb', line 47

def process_resource_callbacks
  self.class.resource_options.each do |resource_option|
    next if skip_action?(resource_option[:options])

    if resource_option[:action] == :load
      load_resource(resource_option[:resource_name], resource_option[:options])
    elsif resource_option[:action] == :authorize
      authorize_resource(resource_option[:resource_name], resource_option[:options])
    elsif resource_option[:action] == :load_and_authorize
      load_resource(resource_option[:resource_name], resource_option[:options])
      authorize_resource(resource_option[:resource_name], resource_option[:options])
    end
  end
end

#resource_attributes(resource, action) ⇒ Object



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/pundit_extraextra/resource_autoload.rb', line 304

def resource_attributes(resource, action)
  attributes = {}

  # Get permitted attributes if they are defined
  if has_permitted_attributes?(resource, action)
    attributes = permitted_attributes(resource)
  else
    candidates = ["#{action}_params", "#{resource_name}_params"]
    candidates.each do |candidate|
      if respond_to?(candidate, true)
        attributes.merge!(send(candidate)) { |key, old_val, new_val| old_val }
        break
      end
    end
  end

  # Extract URL parameters that are part of the resource's attributes
  url_param_keys = request.path_parameters.keys.map(&:to_sym)

  # Remove :id from the keys to ensure it isn't included
  url_param_keys.delete(:id)

  relevant_url_params = params.slice(*url_param_keys).permit!.to_h

  # Merge only the relevant URL parameters that match resource's column names
  relevant_url_params.each do |key, value|
    if resource.class.column_names.include?(key.to_s)
      attributes[key.to_sym] ||= value
    end
  end

  attributes
end

#resource_classObject



296
297
298
# File 'lib/pundit_extraextra/resource_autoload.rb', line 296

def resource_class
  resource_name.classify.constantize
end

#resource_instanceObject



300
301
302
# File 'lib/pundit_extraextra/resource_autoload.rb', line 300

def resource_instance
  instance_variable_get "@#{resource_name}"
end

#resource_nameObject



292
293
294
# File 'lib/pundit_extraextra/resource_autoload.rb', line 292

def resource_name
  controller_name.singularize
end

#skip_action?(options) ⇒ Boolean

Returns:

  • (Boolean)


62
63
64
65
66
# File 'lib/pundit_extraextra/resource_autoload.rb', line 62

def skip_action?(options)
  action = params[:action].to_sym
  (options[:except] && Array(options[:except]).include?(action)) ||
    (options[:only] && !Array(options[:only]).include?(action))
end

#skip_authorization_and_scopeObject



286
287
288
289
290
# File 'lib/pundit_extraextra/resource_autoload.rb', line 286

def skip_authorization_and_scope
  action = params[:action]
  skip_policy_scope if action == 'index'
  skip_authorization
end

#update_resource(current_instance, resource_name, resource_id, find_by_attribute, action) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
# File 'lib/pundit_extraextra/resource_autoload.rb', line 160

def update_resource(current_instance, resource_name, resource_id, find_by_attribute, action)
  resource = current_instance.public_send(resource_name.pluralize).find_by(find_by_attribute => resource_id)
  unless record.nil?
    authorize resource, "#{action}?"
    resource.attributes = resource_attributes(resource, action) if resource.respond_to?(:attributes=)
  else
    resource = nil
  end

  resource
end