Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api keyword without specifying the verb/path loads values from routes #194

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.rst
Expand Up @@ -185,6 +185,9 @@ api
You can use this +api+ method more than once for one method. It could
be useful when there are more routes mapped to it.

When providing just one argument (description) or not argument at all,
the paths will be loaded from routes.rb file.

api_versions (also api_version)
What version(s) does the action belong to. (See `Versioning`_ for details.)

Expand Down
63 changes: 63 additions & 0 deletions lib/apipie/application.rb
Expand Up @@ -4,6 +4,7 @@
module Apipie

class Application
API_METHODS = %w{GET POST PUT PATCH OPTIONS DELETE}

# we need engine just for serving static assets
class Engine < Rails::Engine
Expand All @@ -27,6 +28,68 @@ def set_resource_id(controller, resource_id)
@controller_to_resource_id[controller] = resource_id
end

def apipie_routes
unless @apipie_api_routes
# ensure routes are loaded
Rails.application.reload_routes! unless Rails.application.routes.routes.any?

regex = Regexp.new("\\A#{Apipie.configuration.api_base_url.values.join('|')}")
@apipie_api_routes = Rails.application.routes.routes.select do |x|
if Rails::VERSION::STRING < '3.2.0'
regex =~ x.path.to_s
else
regex =~ x.path.spec.to_s
end
end
end
@apipie_api_routes
end

# the app might be nested when using contraints, namespaces etc.
# this method does in depth search for the route controller
def route_app_controller(app, route)
if app.respond_to?(:controller)
return app.controller(route.defaults)
elsif app.respond_to?(:app)
return route_app_controller(app.app, route)
end
end

def routes_for_action(controller, method)
routes = apipie_routes.select do |route|
controller == route_app_controller(route.app, route) &&
method.to_s == route.defaults[:action]
end

routes.map do |route|
path = if Rails::VERSION::STRING < '3.2.0'
route.path.to_s
else
route.path.spec.to_s
end

path.gsub!('(.:format)', '')
path.gsub!(/[()]/, '')

Apipie.configuration.api_base_url.values.each do |values|
path.gsub!("#{values}/", '/')
end

{ path: path, verb: human_verb(route) }
end
end

def human_verb(route)
verb = API_METHODS.select{|defined_verb| defined_verb =~ /\A#{route.verb}\z/}
if verb.count != 1
verb = API_METHODS.select{|defined_verb| defined_verb == route.constraints[:method]}
if verb.blank?
raise "Unknow verb #{route.path.spec.to_s}"
end
end
verb.first
end

# create new method api description
def define_method_description(controller, method_name, dsl_data)
return if ignored?(controller, method_name)
Expand Down
68 changes: 43 additions & 25 deletions lib/apipie/dsl_definition.rb
Expand Up @@ -20,7 +20,9 @@ def _apipie_dsl_data_clear

def _apipie_dsl_data_init
@_apipie_dsl_data = {
:api => false,
:api_args => [],
:api_from_routes => nil,
:errors => [],
:params => [],
:resouce_id => nil,
Expand Down Expand Up @@ -74,12 +76,29 @@ def def_param_group(name, &block)

# Declare an api.
#
# Example:
# Examples:
# # specify the path explicitly
# api :GET, "/resource_route", "short description",
#
def api(method, path, desc = nil, options={}) #:doc:
# # load paths from routes
# api "short description",
#
# # load paths from routes and don't provide description
# api
#
def api(*args) #:doc:
return unless Apipie.active_dsl?
_apipie_dsl_data[:api_args] << [method, path, desc]
_apipie_dsl_data[:api] = true
case args.size
when 0..1
desc = args.first
_apipie_dsl_data[:api_from_routes] = { :desc => desc }
when 2..3
method, path, desc = *args
_apipie_dsl_data[:api_args] << [method, path, desc]
else
raise ArgumentError, 'Wrong number of arguments'
end
end

# Reference other similar method
Expand Down Expand Up @@ -314,22 +333,27 @@ def _apipie_perform_concern_subst(string)
# create method api and redefine newly added method
def method_added(method_name) #:doc:
super
return if !Apipie.active_dsl? || !_apipie_dsl_data[:api]

if ! Apipie.active_dsl? || _apipie_dsl_data[:api_args].blank?
_apipie_dsl_data_clear
return
if _apipie_dsl_data[:api_from_routes]
desc = _apipie_dsl_data[:api_from_routes][:desc]
api_from_routes = Apipie.routes_for_action(self, method_name).map do |route_info|
[route_info[:verb], route_info[:path], desc]
end
_apipie_dsl_data[:api_args].concat(api_from_routes)
end

begin
# remove method description if exists and create new one
Apipie.remove_method_description(self, _apipie_dsl_data[:api_versions], method_name)
description = Apipie.define_method_description(self, method_name, _apipie_dsl_data)
ensure
_apipie_dsl_data_clear
end
return if _apipie_dsl_data[:api_args].blank?

# remove method description if exists and create new one
Apipie.remove_method_description(self, _apipie_dsl_data[:api_versions], method_name)
description = Apipie.define_method_description(self, method_name, _apipie_dsl_data)

_apipie_dsl_data_clear
_apipie_define_validators(description)
end # def method_added
ensure
_apipie_dsl_data_clear
end
end

module Concern
Expand All @@ -356,18 +380,12 @@ def _apipie_concern_data
def method_added(method_name) #:doc:
super

if ! Apipie.active_dsl? || _apipie_dsl_data[:api_args].blank?
_apipie_dsl_data_clear
return
end

begin
_apipie_concern_data << [method_name, _apipie_dsl_data.merge(:from_concern => true)]
ensure
_apipie_dsl_data_clear
end
return if ! Apipie.active_dsl? || !_apipie_dsl_data[:api]

end # def method_added
_apipie_concern_data << [method_name, _apipie_dsl_data.merge(:from_concern => true)]
ensure
_apipie_dsl_data_clear
end

end

Expand Down
13 changes: 13 additions & 0 deletions spec/controllers/users_controller_spec.rb
Expand Up @@ -30,6 +30,7 @@ def compare_hashes(h1, h2)
it "should contain all resource methods" do
methods = subject._methods
methods.keys.should include(:show)
methods.keys.should include(:create_route)
methods.keys.should include(:index)
methods.keys.should include(:create)
methods.keys.should include(:update)
Expand Down Expand Up @@ -326,6 +327,18 @@ def compare_hashes(h1, h2)
b.full_description.length.should be > 400
end

context "Usign routes.rb" do
it "should contain basic info about method" do
a = Apipie[UsersController, :create_route]
a.apis.count.should == 1
a.formats.should eq(['json'])
api = a.apis.first
api.short_description.should eq("Create user")
api.path.should eq("/users/create_route")
api.http_method.should eq("POST")
end
end

context "contain :see option" do

context "the key is valid" do
Expand Down
10 changes: 10 additions & 0 deletions spec/dummy/app/controllers/users_controller.rb
Expand Up @@ -266,4 +266,14 @@ def see_another
def desc_from_file
render :text => 'document from file action'
end

api 'Create user'
param_group :user
param :user, Hash do
param :permalink, String
end
param :facts, Hash, :desc => "Additional optional facts about the user", :allow_nil => true
def create_route
end

end
6 changes: 5 additions & 1 deletion spec/dummy/config/routes.rb
Expand Up @@ -3,7 +3,11 @@
scope ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do

scope '/api' do
resources :users
resources :users do
collection do
post :create_route
end
end
resources :concerns, :only => [:index, :show]
resources :twitter_example do
collection do
Expand Down