Skip to content

Commit

Permalink
A way to extend an exiting API via concern
Browse files Browse the repository at this point in the history
Sometimes, it's needed to extend an existing controller method with additional
parameters (usually when extending exiting API from plugins/rails engines).
The concern can be also used for this purposed, using `update_api` method.
The params defined in this block are merged with the params of the original method
in the controller this concern is included to.

```ruby
module Concerns
  module OauthConcern
    extend Apipie::DSL::Concern

    update_api(:create, :update) do
 param :user, Hash do
   param :oauth, String, :desc => 'oauth param'
 end
    end
  end
end
```

The concern needs to be included to the controller after the methods are defined
(either at the end of the class, or by using `Controller.send(:include, Concerns::OauthConcern)`.
  • Loading branch information
iNecas committed Aug 21, 2017
1 parent 4e606f5 commit cfb4219
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 9 deletions.
28 changes: 27 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,32 @@ Example
end
Sometimes, it's needed to extend an existing controller method with additional
parameters (usually when extending exiting API from plugins/rails engines).
The concern can be also used for this purposed, using `update_api` method.
The params defined in this block are merged with the params of the original method
in the controller this concern is included to.

Example
~~~~~~~

.. code:: ruby
module Concerns
module OauthConcern
extend Apipie::DSL::Concern
update_api(:create, :update) do
param :user, Hash do
param :oauth, String, :desc => 'oauth param'
end
end
end
end
The concern needs to be included to the controller after the methods are defined
(either at the end of the class, or by using
``Controller.send(:include, Concerns::OauthConcern)``.

=========================
Configuration Reference
Expand Down Expand Up @@ -1231,7 +1257,7 @@ one example per method) by adding a 'title' attribute.
- recorded: true
In RSpec you can add metadata to examples. We can use that feature
to mark selected examples the ones that perform the requests that we want to
to mark selected examples - the ones that perform the requests that we want to
show as examples in the documentation.

For example, we can add ``show_in_doc`` to examples, like this:
Expand Down
57 changes: 51 additions & 6 deletions lib/apipie/dsl_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ module DSL
module Base
attr_reader :apipie_resource_descriptions, :api_params

def _apipie_eval_dsl(*args, &block)
raise 'The Apipie DLS data need to be cleared before evaluating new block' if @_apipie_dsl_data
instance_exec(*args, &block)
return _apipie_dsl_data
ensure
_apipie_dsl_data_clear
end

private

def _apipie_dsl_data
Expand Down Expand Up @@ -381,6 +389,37 @@ def apipie_concern_subst(subst_hash)
_apipie_concern_subst.merge!(subst_hash)
end

# Allows to update existing params after definition was made (usually needed
# when extending the API form plugins).
#
# UsersController.apipie_update_params([:create, :update]) do
# param :user, Hash do
# param :oauth, String
# end
# end
#
# The block is evaluated in scope of the controller. Ohe can pass some additional
# objects via additional arguments like this:
#
# UsersController.apipie_update_params([:create, :update], [:name, :secret]) do |param_names|
# param :user, Hash do
# param_names.each { |p| param p, String }
# end
# end
def apipie_update_params(methods, *args, &block)
methods.each do |method|
method_description = Apipie.get_method_description(self, method)
unless method_description
raise "Could not find method description for #{self}##{method}. Was the method defined?"
end
dsl_data = _apipie_eval_dsl(*args, &block)
params_ordered = dsl_data[:params].map do |args|
Apipie::ParamDescription.from_dsl_data(method_description, args)
end
ParamDescription.unify(method_description.params_ordered_self + params_ordered)
end
end

def _apipie_concern_subst
@_apipie_concern_subst ||= {:controller_path => self.controller_path,
:resource_id => Apipie.get_resource_name(self)}
Expand Down Expand Up @@ -428,12 +467,19 @@ def included(controller)
description = Apipie.define_method_description(controller, method_name, _apipie_dsl_data)
controller._apipie_define_validators(description)
end
_apipie_concern_update_api_blocks.each do |(methods, block)|
controller.apipie_update_params(methods, &block)
end
end

def _apipie_concern_data
@_apipie_concern_data ||= []
end

def _apipie_concern_update_api_blocks
@_apipie_concern_update_api_blocks ||= []
end

def apipie_concern?
true
end
Expand All @@ -449,6 +495,10 @@ def method_added(method_name) #:doc:
_apipie_dsl_data_clear
end

def update_api(*methods, &block)
_apipie_concern_update_api_blocks << [methods, block]
end

end

class ResourceDescriptionDsl
Expand All @@ -461,14 +511,9 @@ def initialize(controller)
@controller = controller
end

def _eval_dsl(&block)
instance_eval(&block)
return _apipie_dsl_data
end

# evaluates resource description DSL and returns results
def self.eval_dsl(controller, &block)
dsl_data = self.new(controller)._eval_dsl(&block)
dsl_data = self.new(controller)._apipie_eval_dsl(&block)
if dsl_data[:api_versions].empty?
dsl_data[:api_versions] = Apipie.controller_versions(controller)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/apipie/method_description.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ def params
params_ordered.reduce(ActiveSupport::OrderedHash.new) { |h,p| h[p.name] = p; h }
end

def params_ordered_self
@params_ordered
end

def params_ordered
all_params = []
parent = Apipie.get_resource_description(@resource.controller.superclass)
Expand Down
15 changes: 14 additions & 1 deletion lib/apipie/param_description.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ def self.from_dsl_data(method_description, args)
&block)
end

def to_s
"ParamDescription: #{method_description.id}##{name}"
end

def ==(other)
return false unless self.class == other.class
if method_description == other.method_description && @options == other.options
true
else
false
end
end

def initialize(method_description, name, validator, desc_or_options = nil, options = {}, &block)

if desc_or_options.is_a?(Hash)
Expand Down Expand Up @@ -146,7 +159,7 @@ def merge_with(other_param_desc)
self
end

# merge param descripsiont. Allows defining hash params on more places
# merge param descriptions. Allows defining hash params on more places
# (e.g. in param_groups). For example:
#
# def_param_group :user do
Expand Down
21 changes: 20 additions & 1 deletion lib/apipie/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ def initialize(param_description)
@param_description = param_description
end

def inspected_fields
[:param_description]
end

def inspect
string = "#<#{self.class.name}:#{self.object_id} "
fields = inspected_fields.map {|field| "#{field}: #{self.send(field)}"}
string << fields.join(", ") << ">"
end

def self.inherited(subclass)
@validators ||= []
@validators.insert 0, subclass
Expand Down Expand Up @@ -67,13 +77,22 @@ def expected_type
end

def merge_with(other_validator)
raise NotImplementedError, "Dont know how to merge #{self.inspect} with #{other_validator.inspect}"
return self if self == other_validator
raise NotImplementedError, "Don't know how to merge #{self.inspect} with #{other_validator.inspect}"
end

def params_ordered
nil
end

def ==(other)
return false unless self.class == other.class
if param_description == other.param_description
true
else
false
end
end
end

# validate arguments type
Expand Down
10 changes: 10 additions & 0 deletions spec/controllers/extended_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require "spec_helper"

describe ExtendedController do

it 'should include params from both original controller and extending concern' do
user_param = Apipie["extended#create"].params[:user]
expect(user_param.validator.params_ordered.map(&:name)).to eq [:name, :password, :from_concern]
end
end

11 changes: 11 additions & 0 deletions spec/dummy/app/controllers/concerns/extending_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Concerns
module ExtendingConcern
extend Apipie::DSL::Concern

update_api(:create) do
param :user, Hash do
param :from_concern, String, :desc => 'param from concern', :allow_nil => false
end
end
end
end
12 changes: 12 additions & 0 deletions spec/dummy/app/controllers/extended_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class ExtendedController < ApplicationController

api :POST, '/extended'
param :user, Hash do
param :name, String
param :password, String
end
def create
end

include Concerns::ExtendingConcern
end

0 comments on commit cfb4219

Please sign in to comment.