I have written a simple API but something feels wrong about the routes and the APIs. Its my first time am writing an API and just a few months writing ROR code. Take a look at the code below and help me straighten it!!
I wish the API to respond to requests like https://api.xxxxx.com/v1/xml?username=u_name&password=passwd& to=xxxxx&from=xxxx&message=text
The subdomain does not seem to be working either;
Routes
namespace :api, :path => "", constraints: {subdomain: 'api'} do
namespace :v1 do
namespace :json, defaults: {format: :json} do
resources :messages
resources :balance
end
namespace :xml, defaults: {format: :xml} do
resources :messages
resources :balance
end
end
end
These are the controllers for the json API
Messages Controller:
class Api::V1::Json::MessagesController < ApplicationController
before_filter :authenticate
def show
end
def create
#user = current_user
#message = Message.new(params[:message])
if #message.save
MessageWorker.perform_async(#message.id, lists, current_user.id)
render json: {status: "success", to: #message.count, from: #message.from,
balance: #user.balance, message: #message.message},
time: #message.created_at}
else
render status: 400
end
end
def authenticate
authenticate_or_request_with_http_basic do |username,password|
resource = User.find_by_email(username)
if resource.valid_password?(password)
sign_in :user, resource
end
end
end
end
Balance Controller:
class Api::V1::Json::BalanceController < ApplicationController
before_filter :authenticate
def show
#balance = current_user.balance
render json: {user: current_user.name.titleize, balance: "#{current_user.currency}: #{#balance}"}
end
def authenticate
authenticate_or_request_with_http_basic do |username,password|
resource = User.find_by_email(username)
if resource.valid_password?(password)
sign_in :user, resource
end
end
end
end
Do:
constraints subdomain: 'api' do
scope module: :api do
namespace :v1 do
You'd rather not namespace json or xml, it makes no sense: the same controllers should handle both request, just a matter of format.
Rails makes it really easy with respond_to
Related
I'm trying to create an API, but I get the error
"type": "NameError",
"message": "uninitialized constant Api::V1::ReservationOptionsController::ReservationOptions",
I cannot seem to find the issue here.
Code
routes
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :reservation_options, only: [:show, :create]
end
end
controllers/api/v1/reservation_options_controller.rb
class Api::V1::ReservationOptionsController < Api::V1::BaseController
acts_as_token_authentication_handler_for User, only: [:create]
def show
#reservation_option = ReservationOption.find(params[:id])
#reservation = #reservation_option.reservation
authorize #reservation_option
end
def create
#user = current_user
#reservation_option = ReservationOptions.new(reservation_option_params)
authorize #reservation_option
if #reservation_option.save
render :show, status: :created
else
render_error
end
end
private
def reservation_option_params
params.require(:reservation_option).permit(:option_id, :option_quantity, :reservation_id)
end
end
You have an error in the action create
def create
#user = current_user
# change this line
# #reservation_option = ReservationOptions.new(reservation_option_params)
#reservation_option = ReservationOption.new(reservation_option_params)
authorize #reservation_option
if #reservation_option.save
render :show, status: :created
else
render_error
end
end
It looks like ReservationOptions hasn't been defined anywhere, and you're using it in controllers/api/v1/reservation_options_controller.rb.
Make sure you've spelled it right, or that you have the appropriate model in app/models/reservation_option.rb. My guess is that it should be ReservationOption, since Rails model class names are typically singular.
I'm beginner of Ruby on Rails.
I've added gems, Devise and ActiveAdmin, to make management screen and to make authentication system to my api.
At first, I added ActiveAdmin. After db-migrated, I requested localhost:3000/admin/login and it returned 200(OK) status and the login interface to me.
Next, I added Devise. After some coding for authentication and db-migration, localhost:3000/admin/login returned 401 status to me and "{"error":"You need to sign in or sign up before continuing."}" was written at localhost:3000/admin/login page. And absolutely It didn't show the login interface to me.
Here, I don't know why authorization error happened before authorization.
Mac OS version: 10.13.1
Ruby version: 2.4.2
Gem version: 2.7.4
Rails version: 5.1.6
application_contorller.rb
class ApplicationController < ActionController::Base
include AbstractController::Translation
before_action :authenticate_user_from_token!
respond_to :json
def authenticate_user_from_token!
auth_token = request.headers['Authorization']
if auth_token
authenticate_with_auth_token(auth_token)
else
authenticate_error
end
end
private
def authenticate_with_auth_token(auth_token)
unless auth_token.include?(':')
authenticate_error
return
end
user_id = auth_token.split(':').first
user = User.where(id: user_id).first
#current_user = user
if user && Devise.secure_compare(user.access_token, auth_token)
# ログインを許可
sign_in user, store: false
else
authenticate_error
render json: {error: "hello_error"}, status: 401
end
end
def authenticate_error
render json: {error: t('devise.failure.unauthenticated')}, status: 401
end
end
app/controller/v1/sessions_controller.rb
module V1
class SessionsController < ApiController
skip_before_action :authenticate_user_from_token!
# POST /v1/login
def create
#user = User.find_for_database_authentication(email: params[:email])
return invalid_email unless #user
if #user.valid_password?(params[:password])
sign_in :user, #user
render json: #user, serializer: SessionSerializer, root: nil
else
invalid_password
end
end
def invalid_email
warden.custom_failure
render json: {error: t('invalid_email')}
end
def invalid_password
warden.custom_failure
render json: {error: t('invalid_password')}
end
end
end
config/routes.rb
Rails.application.routes.draw do
devise_for :admin_users, ActiveAdmin::Devise.config
ActiveAdmin.routes(self)
devise_for :users
namespace :admin, defaults: {format: :json} do
delete 'sign_out', to: "sessions#destroy"
resources :login, only: [:create], controller: :sessions
end
namespace :v1, defaults: {format: :json} do
resources :login, only: [:create], controller: :sessions
end
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
I am getting a routing error when I attempt to create a new db entry or update a current one.
ERROR: No route matches [POST] "/pubs"
Routes.rb:
resources :people, except: [:show] do
resources :pubs, except: [:create, :new, :edit, :destroy]
end
resources :articles
resources :pubs, except: [:create, :new, :edit, :destroy]
namespace :sekret do
resources :people do
resources :pubs
end
end
sekret/pubs_controller
class Sekret::PubsController < SekretController
def index
#pubs = Pub.all
end
def show
#pub = Pub.find(params[:id])
end
def new
#person = Person.find(params[:person_id])
#pub = #person.pubs.new
end
def create
#pub = Pub.new(pub_params)
if #pub.save
flash[:notice] = "Article created successfully!"
redirect_to sekret_person_pub_path(#pub)
else
render :new, status: :unprocessable_entity
end
end
def edit
#pub = Pub.find(params[:id])
end
def update
#pub = Pub.find(params[:id])
if #pub.update(pub_params)
redirect_to sekret_person_pub_path(#pub)
else
render :edit, status: :unprocessable_entity
end
end
def destroy
pub = Pub.find(params[:id])
pub.destroy
redirect_to sekret_people_path
end
private
def pub_params
params.require(:pub).permit(
:pubmed_id, :journal, :pages, :date, :type, :link, :authors,
:title, :notes, :auth_id, :person_id)
end
end
After going through all of this setup, when I allow the non-namespace pubs to resolve edit, update, etc, the update process goes through without a hitch. Once I limit these functions to within the password protected namespace I get the routing error. After parsing through the routes I can see that sekret_person_pub_path is listed there. I think I am missing something somewhere.
Rake Routes:
pubs#index
pub GET /pubs/:id(.:format) pubs#show
PATCH /pubs/:id(.:format) pubs#update
PUT /pubs/:id(.:format) pubs#update
sekret_person_pubs GET /sekret/people/:person_id/pubs(.:format) sekret/pubs#index
POST /sekret/people/:person_id/pubs(.:format) sekret/pubs#create
new_sekret_person_pub GET /sekret/people/:person_id/pubs/new(.:format) sekret/pubs#new
edit_sekret_person_pub GET /sekret/people/:person_id/pubs/:id/edit(.:format) sekret/pubs#edit
sekret_person_pub GET /sekret/people/:person_id/pubs/:id(.:format) sekret/pubs#show
PATCH /sekret/people/:person_id/pubs/:id(.:format) sekret/pubs#update
PUT /sekret/people/:person_id/pubs/:id(.:format) sekret/pubs#update
DELETE /sekret/people/:person_id/pubs/:id(.:format) sekret/pubs#destroy
sekret_people GET /sekret/people(.:format)
By using resources :pubs, except: [:create, :new, :edit, :destroy], you are preventing the route generation from providing POST /pubs.
The namespace and nested resources will generate a URL POST sekret/people/:person_id/pubs.
In your controller, you should create the Pub as an associated object.
def create
person = Person.find(params[:person_id])
#pub = person.pubs.new(pub_params)
if #pub.save
flash[:notice] = "Article created successfully!"
redirect_to sekret_person_pub_path(#pub)
else
render :new, status: :unprocessable_entity
end
end
If you want to restrict access the create method, you could use an authorization library such as Pundit in which case you would setup a policy to restrict who can do what.
https://github.com/elabs/pundit
You are missing out on the routes because rails form don't use the correct routes when namespacing so you'll have to specify them manually
<%= form for #pub, url: sekret_person_pubs_path do |f| %>
to let the form knows which route to post, if you do not specify the url, rails will use url: person_pubs_path behind the scenes
Edit: forgot to add _path
I know there are already a lot of questions about this on stackoverflow, but none of them works in my case.
In my routes.rb
Exer9::Application.routes.draw do
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
end
end
end
exer9/app/controllers/api/v1/users_controller.rb
module Api
module v1
class UsersController < ApplicationController
# GET /user
# GET /user.json
def index
#users = User.all
render json: #users
end
def new
end
def update
end
# GET /user/1
# GET /user/1.json
def show
#user = User.find(params[:id])
render json: #user
end
def create
#user = User.new(params[:user])
if #user.save
render json: #user
else
render json: #user.errors
end
end
def delete
end
def destroy
end
end
end
end
Update
This is my ApplicationController file
class ApplicationController < ActionController::API
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
# protect_from_forgery with: :exception
end
The error message that I get is:
superclass mismatch for class UsersController
Extracted source (around line #2):
1
2
3
4
5
6
class Api::V1::UsersController < ApplicationController
# GET /user
# GET /user.json
def index
Rails.root: /home/steven/Desktop/weekly-exercises/exer9
Application Trace | Framework Trace | Full Trace
app/controllers/api/v1/users_controller.rb:2:in `<top (required)>
'
Any help here is really appreciated!
Use shorter syntax, like:
class Api::V1::UsersController < ApplicationController
# your code goes here
end
Also, do you restart rails server after renaming classes and files?
Make sure your folder structure is correct:
users_controller.rb
should be found:
app/controllers/api/v1/users_controller.rb
You can use it like this
In routes.rb
namespace :api, defaults: {format: 'json'} do
scope module: :v1, constraints: ApiConstraints.new(version: 1 , default: true) do
resources :users
end
end
In controller
class Api::V1::UsersController < ApplicationController
# Your code here
end
I use the following routes configuration in a Rails 3 application.
# config/routes.rb
MyApp::Application.routes.draw do
resources :products do
get 'statistics', on: :collection, controller: "statistics", action: "index"
end
end
The StatisticController has two simple methods:
# app/controllers/statistics_controller.rb
class StatisticsController < ApplicationController
def index
#statistics = Statistic.chronologic
render json: #statistics
end
def latest
#statistic = Statistic.latest
render json: #statistic
end
end
This generates the URL /products/statistics which is successfully handled by the StatisticsController.
How can I define a route which leads to the following URL: /products/statistics/latest?
Optional: I tried to put the working definition into a concern but it fails with the error message:
undefined method 'concern' for #<ActionDispatch::Routing::Mapper ...
I think you can do it by two ways.
method 1:
resources :products do
get 'statistics', on: :collection, controller: "statistics", action: "index"
get 'statistics/latest', on: :collection, controller: "statistics", action: "latest"
end
method 2, if you have many routes in products, you should use it for better organized routes:
# config/routes.rb
MyApp::Application.routes.draw do
namespace :products do
resources 'statistics', only: ['index'] do
collection do
get 'latest'
end
end
end
end
and put your StatisticsController in a namespace:
# app/controllers/products/statistics_controller.rb
class Products::StatisticsController < ApplicationController
def index
#statistics = Statistic.chronologic
render json: #statistics
end
def latest
#statistic = Statistic.latest
render json: #statistic
end
end