I am practicing to make API in rails. The api has currently only one end point which receives a url via get request. My routes are:
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
namespace :api, defaults: { format: :json } do
namespace :v1 do # resources :orders
get "*link" => "application#parse_link"
end
end
end
My Application Controller Code:
require 'open-uri'
class Api::V1::ApplicationController < ActionController::Base
respond_to :json
# protect_from_forgery with: :exception
def parse_link
begin
url = URI.parse(params[:link])
doc = Nokogiri::HTML(open(url).read)
rescue
redirect_to 'http://localhost:3000/'
end
end
end
When I send urls like this:
http://localhost:3000/api/v1/http://stackoverflow.com/questions/41138538/dynamic-urls-in-rails-params
It works fine
But the following type of urls does not work:
http://localhost:3000/api/v1/http://stackoverflow.com/
In this case, it splits the link and give me these params
<ActionController::Parameters {"link"=>"http:/stackoverflow", "format"=>"com"} permitted: true>
As you can see it splits the given url and save half of it in link param and the ".com" part in format params.
Thanks
Try changing defaults to constraints:
Rails.application.routes.draw do
namespace :api, constraints: { format: :json } do
namespace :v1 do # resources :orders
get "*link" => "application#parse_link"
end
end
end
Notice: it will constraint format to be json, not only set it as default. Rails treated .com at end of your URL as response type.
Related
I am experimenting the Rails API with devise.
I am trying to create a POST request so that the user can autenticate using the email and password. To do so, I am using devise and simple token authentication
However, when I submit my POST request using postman, I get the error:
ActionController::RoutingError (No route matches [POST] "/v1/sessions"):
I think the issue is that is sending the post to: /v1/sessions rather than api/v1/sessions.
However, I do not understand why since I declared my routes such as: api-->v1-->sessions
Folder structure of controller
Routes
Rails.application.routes.draw do
# devise_for :users
namespace :api do
namespace :v1 do
resources :sessions, only: [:create, :destroy]
end
end
end
Controller
class V1::SessionsController < ApplicationController
def create
user = User.where(email: params[:email]).first
if user&.valid_password?(params[:password])
render json: user.as_json(only: [:email, :authentication_token]), status: :created
else
head(:unauthorized)
end
end
def destroy
end
end
shoud it be class Api::V1::SessionsController < ApplicationController instead?
In my API, I've implemented the following way of showing a users account in JSON (very simple).
class API::V1::UsersController < ApplicationController
respond_to :json
def show
respond_with User.find(params[:id])
end
end
This is my routes.rb
Rails.application.routes.draw do
devise_for :users
# Api definition
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :users, :only => [:show]
end
end
end
As of now, this works by allowing my to browse to the URL: http://localhost/api/v1/users/1 to show the user account with ID 1. What I want is to be able to type http://localhost/api/v1/users/show?id=1 to allow for the possibility of specifying more than just the one parameter to the show method.
I've setup a rails application that expects the parameters to be specified in this way in the past but this time around it's not working. I'm assuming it's something to do with the way I've defined the route in my routes.rb (first time I'm using the resource do notation). Any help would be greatly appreciated. thanks!
Figured it out.
Routes.rb:
Rails.application.routes.draw do
devise_for :users
# Api definition
namespace :api, defaults: { format: :json } do
namespace :v1 do
#resources :users, :only => [:show]
get '/users/show/' => 'users#show'
end
end
end
Rspec fails with ActionController::UrlGenerationError with a URL I would think is valid. I've tried messing with the params of Rspec request, as well as fiddled with the routes.rb, but I'm still missing something.
The weird thing is, it works 100% as expected when testing locally with curl.
Error:
Failure/Error: get :index, {username: #user.username}
ActionController::UrlGenerationError:
No route matches {:action=>"index", :controller=>"api/v1/users/devices", :username=>"isac_mayer"}
Relevant code:
spec/api/v1/users/devices_controller_spec.rb
require 'rails_helper'
RSpec.describe Api::V1::Users::DevicesController, type: :controller do
before do
#user = FactoryGirl::create :user
#device = FactoryGirl::create :device
#user.devices << #device
#user.save!
end
describe "GET" do
it "should GET a list of devices of a specific user" do
get :index, {username: #user.username} # <= Fails here, regardless of params. (Using FriendlyId by the way)
# expect..
end
end
end
app/controllers/api/v1/users/devices_controller.rb
class Api::V1::Users::DevicesController < Api::ApiController
respond_to :json
before_action :authenticate, :check_user_approved_developer
def index
respond_with #user.devices.select(:id, :name)
end
end
config/routes.rb
namespace :api, path: '', constraints: {subdomain: 'api'}, defaults: {format: 'json'} do
namespace :v1 do
resources :checkins, only: [:create]
resources :users do
resources :approvals, only: [:create], module: :users
resources :devices, only: [:index, :show], module: :users
end
end
end
Relevant line from rake routes
api_v1_user_devices GET /v1/users/:user_id/devices(.:format) api/v1/users/devices#index {:format=>"json", :subdomain=>"api"}
The index action requires a :user_id parameter, but you haven't supplied one in the params hash. Try:
get :index, user_id: #user.id
The error message is a bit confusing, because you aren't actually supplying a URL; instead you are calling the #get method on the test controller, and passing it a list of arguments, the first one is the action (:index), and the second is the params hash.
Controller specs are unit tests for controller actions, and they expect that the request parameters are correctly specified. Routing is not the responsibility of the controller; if you want to verify that a particular URL is routed to the right controller action (since as you mention, you are using friendly-id), you may want to consider a routing spec.
Attempting to create an API using doorkeeper. When I sign into my account and the session user is authenticated and access my API on
http://localhost:3000/api/v1/trials
I get a routing error page saying "uninitialized constant TrialsController" and a rake routes listing. Including:
trials_path GET /api/v1/trials(.:format) trials#index
I am using rails version 4.0.3, ruby 2.0.0. This is my config/routes.rb file:
MyApp::Application.routes.draw do
devise_for :users
use_doorkeeper :scope => 'oauth2' do
end
root :to => 'welcome#index'
scope 'api' do
scope 'v1' do
resources :trials
end
end
end
My app/ dir contains:
app/
controllers/
application_controller.rb
welcome_controller.rb
api/
v1/
trials_controller.rb
My trials_controller.rb is:
module Api::V1
class TrialsController < ::ApplicationController
doorkeeper_for :all
def index
#trials = Trials.all
end
def show
...
end
...
end
end
UPDATE:
When I change the routes.rb to namespace the trails controller like so:
namespace :api do
namespace :v1 do
resources :trails
end
end
I get a "no route matches" error when attempting to access:
http://localhost:3000/api/v1/trials(.json)
(With or without the extension.)
I have also added to trials#index:
def index
#trials = Trials.all
respond_to do |format|
format.json { render :json => #trials }
format.xml { render :xml => #trials }
end
end
Also with no luck.
I'm not sure how it would play out against doorkeeper but I have a similar API structure in an app. I have the following in my routes (matching what Sergio notes)
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :api_controller_1
resources :api_controller_2
end
end
And here's how I build my API classes:
module Api
module V1
class ApiNamedController < ApplicationController
# code
end
end
end
I try to tell rails 3.2 that it should render JSON by default, and kick HTML completely like this:
respond_to :json
def index
#clients = Client.all
respond_with #clients
end
With this syntax, I have to add .json to the URL. How can I achieve it?
You can modify your routes.rb files to specify the default format
routes.rb
resources :clients, defaults: {format: :json}
This will modify the default response format for your entire clients_controller
This pattern works well if you want to use the same controller actions for both. Make a web version as usual, using :html as the default format. Then, tuck the api under a path and set :json as the default there.
Rails.application.routes.draw do
resources :products
scope "/api", defaults: {format: :json} do
resources :products
end
end
If you don't need RESTful responding in your index action then simply render your xml response directly:
def index
render json: Client.all
end
Extending Mark Swardstrom's answer, if your rails app is an API that always returns JSON responses, you could simply do
Rails.application.routes.draw do
scope '/', defaults: { format: :json } do
resources :products
end
end