Class: Apiwork::API::Resource

Inherits:
Object
  • Object
show all
Defined in:
lib/apiwork/api/resource.rb,
lib/apiwork/api/resource/action.rb

Overview

Block context for defining API resources and routes.

Resource provides the DSL available inside ‘resources` and `resource` blocks. Methods include nested resources, custom actions, and concerns.

Examples:

Defining resources with actions

Apiwork::API.define '/api/v1' do
  resources :invoices do
    member do
      post :send
      get :preview
    end

    collection do
      get :search
    end

    resources :items
  end
end

Defined Under Namespace

Classes: Action

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_class, constraints: nil, contract_class_name: nil, controller: nil, defaults: nil, except: nil, name: nil, only: nil, param: nil, path: nil, singular: false) ⇒ Resource

Returns a new instance of Resource.



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
# File 'lib/apiwork/api/resource.rb', line 41

def initialize(
  api_class,
  constraints: nil,
  contract_class_name: nil,
  controller: nil,
  defaults: nil,
  except: nil,
  name: nil,
  only: nil,
  param: nil,
  path: nil,
  singular: false
)
  @api_class = api_class
  @constraints = constraints
  @contract_class_name = contract_class_name
  @controller = controller
  @defaults = defaults
  @except = except
  @name = name
  @only = only
  @param = param
  @path = path
  @singular = singular

  @crud_actions = name ? determine_crud_actions(singular, except:, only:) : []
  @custom_actions = []
  @resources = {}
  @concerns = {}
  @resource_stack = []
  @current_options = nil
  @in_member_block = false
  @in_collection_block = false
end

Instance Attribute Details

#api_classObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def api_class
  @api_class
end

#constraintsObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def constraints
  @constraints
end

#contract_classObject



39
40
41
# File 'lib/apiwork/api/resource.rb', line 39

def contract_class
  @contract_class
end

#contract_class_nameObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def contract_class_name
  @contract_class_name
end

#controllerObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def controller
  @controller
end

#defaultsObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def defaults
  @defaults
end

#exceptObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def except
  @except
end

#nameObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def name
  @name
end

#onlyObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def only
  @only
end

#paramObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def param
  @param
end

#pathObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def path
  @path
end

#singularObject (readonly)



27
28
29
# File 'lib/apiwork/api/resource.rb', line 27

def singular
  @singular
end

Instance Method Details

#actionsObject



88
89
90
# File 'lib/apiwork/api/resource.rb', line 88

def actions
  @actions ||= build_actions
end

#add_action(action_name, method:, type:) ⇒ Object



100
101
102
# File 'lib/apiwork/api/resource.rb', line 100

def add_action(action_name, method:, type:)
  @custom_actions << Action.new(action_name, method:, type:)
end

#add_resource(resource) ⇒ Object



104
105
106
# File 'lib/apiwork/api/resource.rb', line 104

def add_resource(resource)
  @resources[resource.name] = resource
end

#collection {|resource| ... } ⇒ void

This method returns an undefined value.

Block for defining collection actions.

Collection routes don’t include :id: ‘/invoices/action`

Examples:

instance_eval style

collection do
  get :search
  post :bulk_create
end

yield style

collection do |collection|
  collection.get :search
  collection.post :bulk_create
end

Yields:

  • block with HTTP verb methods

Yield Parameters:



376
377
378
379
380
# File 'lib/apiwork/api/resource.rb', line 376

def collection(&block)
  @in_collection_block = true
  block.arity.positive? ? yield(self) : instance_eval(&block)
  @in_collection_block = false
end

#collection_actionsObject



96
97
98
# File 'lib/apiwork/api/resource.rb', line 96

def collection_actions
  @custom_actions.select(&:collection?).index_by(&:name)
end

#concern(concern_name, callable = nil) {|resource| ... } ⇒ void

This method returns an undefined value.

Defines a reusable concern.

Examples:

instance_eval style

concern :commentable do
  resources :comments
end

resources :posts, concerns: [:commentable]

yield style

concern :commentable do |resource|
  resource.resources :comments
end

resources :posts, concerns: [:commentable]

Parameters:

  • concern_name (Symbol)

    The concern name.

  • callable (Proc, nil) (defaults to: nil)

    (nil) Optional callable instead of block.

Yields:

  • block with resource definitions

Yield Parameters:



484
485
486
487
488
489
490
491
492
493
# File 'lib/apiwork/api/resource.rb', line 484

def concern(concern_name, callable = nil, &block)
  callable ||= lambda do |resource, options|
    if block.arity.positive?
      yield(resource, options)
    else
      resource.instance_exec(options, &block)
    end
  end
  @concerns[concern_name] = callable
end

#concerns(*concern_names, **options) ⇒ void

This method returns an undefined value.

Includes previously defined concerns.

Examples:

resources :posts do
  concerns :commentable, :taggable
end

Parameters:

  • concern_names (Array<Symbol>)

    The concern names to include.

  • options (Hash)

    ({}) The options passed to the concern.



508
509
510
511
512
513
514
515
# File 'lib/apiwork/api/resource.rb', line 508

def concerns(*concern_names, **options)
  concern_names.flatten.each do |concern_name|
    callable = @concerns[concern_name]
    raise ConfigurationError, "No concern named :#{concern_name} was found" unless callable

    callable.call(self, options)
  end
end

#delete(action_names, on: nil) ⇒ void

This method returns an undefined value.

Defines a DELETE action.

Examples:

member { delete :archive }

Parameters:

  • action_names (Symbol, Array<Symbol>)

    The action name(s).

  • on (Symbol, nil) (defaults to: nil)

    (nil) [:collection, :member] The scope.



456
457
458
# File 'lib/apiwork/api/resource.rb', line 456

def delete(action_names, on: nil)
  capture_actions(action_names, on:, method: :delete)
end

#each_resource(&block) ⇒ Object



135
136
137
138
139
140
# File 'lib/apiwork/api/resource.rb', line 135

def each_resource(&block)
  @resources.each_value do |resource|
    yield resource
    resource.each_resource(&block)
  end
end

#find_resource(resource_name = nil, &block) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/apiwork/api/resource.rb', line 108

def find_resource(resource_name = nil, &block)
  return find_resource_by_block(&block) if block
  return @resources[resource_name] if @resources[resource_name]

  @resources.each_value do |resource|
    found = resource.find_resource(resource_name)
    return found if found
  end

  nil
end

#find_resource_for_path(resource_path) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/apiwork/api/resource.rb', line 120

def find_resource_for_path(resource_path)
  current = nil
  resource_path.split('/').each do |part|
    next if part.empty?

    resource_name = part.tr('-', '_').to_sym
    target = current ? current.resources : @resources
    found = target[resource_name] || target[resource_name.to_s.singularize.to_sym]
    next unless found

    current = found
  end
  current
end

#get(action_names, on: nil) ⇒ void

This method returns an undefined value.

Defines a GET action.

Examples:

Inside member block

member { get :preview }

With on parameter

get :search, on: :collection

Parameters:

  • action_names (Symbol, Array<Symbol>)

    The action name(s).

  • on (Symbol, nil) (defaults to: nil)

    (nil) [:collection, :member] The scope.



396
397
398
# File 'lib/apiwork/api/resource.rb', line 396

def get(action_names, on: nil)
  capture_actions(action_names, on:, method: :get)
end

#index_actions?Boolean

Returns:

  • (Boolean)


76
77
78
# File 'lib/apiwork/api/resource.rb', line 76

def index_actions?
  @resources.values.any? { |resource| resource.actions.key?(:index) || resource.index_actions? }
end

#member {|resource| ... } ⇒ void

This method returns an undefined value.

Block for defining member actions (operate on :id).

Member routes include :id in the path: ‘/invoices/:id/action`

Examples:

instance_eval style

member do
  post :send
  get :preview
end

yield style

member do |member|
  member.post :send
  member.get :preview
end

Yields:

  • block with HTTP verb methods

Yield Parameters:



350
351
352
353
354
# File 'lib/apiwork/api/resource.rb', line 350

def member(&block)
  @in_member_block = true
  block.arity.positive? ? yield(self) : instance_eval(&block)
  @in_member_block = false
end

#member_actionsObject



92
93
94
# File 'lib/apiwork/api/resource.rb', line 92

def member_actions
  @custom_actions.select(&:member?).index_by(&:name)
end

#patch(action_names, on: nil) ⇒ void

This method returns an undefined value.

Defines a PATCH action.

Examples:

member { patch :mark_paid }

Parameters:

  • action_names (Symbol, Array<Symbol>)

    The action name(s).

  • on (Symbol, nil) (defaults to: nil)

    (nil) [:collection, :member] The scope.



426
427
428
# File 'lib/apiwork/api/resource.rb', line 426

def patch(action_names, on: nil)
  capture_actions(action_names, on:, method: :patch)
end

#post(action_names, on: nil) ⇒ void

This method returns an undefined value.

Defines a POST action.

Examples:

member { post :send }

Parameters:

  • action_names (Symbol, Array<Symbol>)

    The action name(s).

  • on (Symbol, nil) (defaults to: nil)

    (nil) [:collection, :member] The scope.



411
412
413
# File 'lib/apiwork/api/resource.rb', line 411

def post(action_names, on: nil)
  capture_actions(action_names, on:, method: :post)
end

#put(action_names, on: nil) ⇒ void

This method returns an undefined value.

Defines a PUT action.

Examples:

member { put :replace }

Parameters:

  • action_names (Symbol, Array<Symbol>)

    The action name(s).

  • on (Symbol, nil) (defaults to: nil)

    (nil) [:collection, :member] The scope.



441
442
443
# File 'lib/apiwork/api/resource.rb', line 441

def put(action_names, on: nil)
  capture_actions(action_names, on:, method: :put)
end

#representation_classObject



84
85
86
# File 'lib/apiwork/api/resource.rb', line 84

def representation_class
  contract_class&.representation_class
end

#representation_classesObject



80
81
82
# File 'lib/apiwork/api/resource.rb', line 80

def representation_classes
  @representation_classes ||= collect_all_representation_classes
end

#resolve_contract_classObject



142
143
144
145
146
147
148
149
# File 'lib/apiwork/api/resource.rb', line 142

def resolve_contract_class
  return @contract_class if @contract_class
  return nil unless @contract_class_name

  @contract_class = @contract_class_name.constantize
rescue NameError
  nil
end

#resource(resource_name, concerns: nil, constraints: nil, contract: nil, controller: nil, defaults: nil, except: nil, only: nil, param: nil, path: nil) {|resource| ... } ⇒ void

This method returns an undefined value.

Defines a singular resource (no index, no :id in URL).

Default actions: :show, :create, :update, :destroy.

Examples:

instance_eval style

resource :profile do
  resources :settings
end

yield style

resource :profile do |resource|
  resource.resources :settings
end

Parameters:

  • resource_name (Symbol)

    The resource name (singular).

  • concerns (Array<Symbol>, nil) (defaults to: nil)

    (nil) The concerns to include.

  • constraints (Hash, Proc, nil) (defaults to: nil)

    (nil) The route constraints.

  • contract (String, nil) (defaults to: nil)

    (nil) The custom contract path.

  • controller (String, nil) (defaults to: nil)

    (nil) The custom controller path.

  • defaults (Hash, nil) (defaults to: nil)

    (nil) The default route parameters.

  • except (Array<Symbol>, nil) (defaults to: nil)

    (nil) The actions to exclude.

  • only (Array<Symbol>, nil) (defaults to: nil)

    (nil) The CRUD actions to include.

  • param (Symbol, nil) (defaults to: nil)

    (nil) The custom ID parameter.

  • path (String, nil) (defaults to: nil)

    (nil) The custom URL segment.

Yields:

  • block for nested resources and custom actions

Yield Parameters:



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/apiwork/api/resource.rb', line 266

def resource(
  resource_name,
  concerns: nil,
  constraints: nil,
  contract: nil,
  controller: nil,
  defaults: nil,
  except: nil,
  only: nil,
  param: nil,
  path: nil,
  &block
)
  options = {
    constraints:,
    contract:,
    controller:,
    defaults:,
    except:,
    only:,
    param:,
    path:,
  }.compact
  build_resource(resource_name, options:, singular: true)

  @resource_stack.push(resource_name)

  self.concerns(*concerns) if concerns
  if block
    block.arity.positive? ? yield(self) : instance_eval(&block)
  end

  @resource_stack.pop
end

#resources(resource_name = nil, concerns: nil, constraints: nil, contract: nil, controller: nil, defaults: nil, except: nil, only: nil, param: nil, path: nil) {|resource| ... } ⇒ Hash{Symbol => Resource}

Defines a plural resource with standard CRUD actions.

Default actions: :index, :show, :create, :update, :destroy.

Examples:

instance_eval style

resources :invoices do
  member { get :preview }
  resources :items
end

yield style

resources :invoices do |resource|
  resource.member { |member| member.get :preview }
  resource.resources :items
end

Parameters:

  • resource_name (Symbol) (defaults to: nil)

    The resource name (plural).

  • concerns (Array<Symbol>, nil) (defaults to: nil)

    (nil) The concerns to include.

  • constraints (Hash, Proc, nil) (defaults to: nil)

    (nil) The route constraints.

  • contract (String, nil) (defaults to: nil)

    (nil) The custom contract path.

  • controller (String, nil) (defaults to: nil)

    (nil) The custom controller path.

  • defaults (Hash, nil) (defaults to: nil)

    (nil) The default route parameters.

  • except (Array<Symbol>, nil) (defaults to: nil)

    (nil) The actions to exclude.

  • only (Array<Symbol>, nil) (defaults to: nil)

    (nil) The CRUD actions to include.

  • param (Symbol, nil) (defaults to: nil)

    (nil) The custom ID parameter.

  • path (String, nil) (defaults to: nil)

    (nil) The custom URL segment.

Yields:

  • block for nested resources and custom actions

Yield Parameters:

Returns:



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/apiwork/api/resource.rb', line 191

def resources(
  resource_name = nil,
  concerns: nil,
  constraints: nil,
  contract: nil,
  controller: nil,
  defaults: nil,
  except: nil,
  only: nil,
  param: nil,
  path: nil,
  &block
)
  return @resources if resource_name.nil?

  options = {
    constraints:,
    contract:,
    controller:,
    defaults:,
    except:,
    only:,
    param:,
    path:,
  }.compact
  build_resource(resource_name, options:, singular: false)

  @resource_stack.push(resource_name)

  self.concerns(*concerns) if concerns
  if block
    block.arity.positive? ? yield(self) : instance_eval(&block)
  end

  @resource_stack.pop
end

#with_options(options = {}) {|resource| ... } ⇒ void

This method returns an undefined value.

Applies options to all resources defined in the block.

Examples:

instance_eval style

with_options only: [:index, :show] do
  resources :reports
  resources :analytics
end

yield style

with_options only: [:index, :show] do |resource|
  resource.resources :reports
  resource.resources :analytics
end

Parameters:

  • options (Hash) (defaults to: {})

    ({}) The options to merge into nested resources.

Yields:

  • block with resource definitions

Yield Parameters:



321
322
323
324
325
326
327
328
# File 'lib/apiwork/api/resource.rb', line 321

def with_options(options = {}, &block)
  old_options = @current_options
  @current_options = merged_options(options)

  block.arity.positive? ? yield(self) : instance_eval(&block)

  @current_options = old_options
end