I have two API controllers in my Rails app for a RESTful setup:
StoresController (has many products)
ProductsController (has one store)
How can I write the API so that
http://localhost:3000/api/v1/stores/37/products
returns only the products for that store(in this case store #37)? I think I'm missing a route and controller method to make that happen.
Routes
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :stores
resources :licenses
end
end
API Controllers
APIController:
module Api
module V1
class ApiController < ApplicationController
respond_to :json
before_filter :restrict_access
private
def restrict_access
api_app = ApiApp.find_by_access_token(params[:access_token])
head :unauthorized unless api_app
end
end
end
end
StoresController:
module Api
module V1
class StoresController < ApiController
def index
respond_with Store.all
end
def show
respond_with Store.find_by_id(params[:id])
end
end
end
end
ProductsController:
module Api
module V1
class ProductsController < ApiController
def index
respond_with Product.all
end
def show
respond_with Product.find_by_id(params[:id])
end
end
end
end
Thanks for any insight.
You want nested resources in your routes:
resources :stores do
resources :products
end
So you have those routes:
GET /stores/:id
GET/POST /stores/:store_id/products
PUT/DELETE /stores/:store_id/products/:id
You'll may also want shallow routes, to avoid deeply nested resources:
resources :stores, shallow:true do
resources :products
end
So you have those routes:
GET /stores/:id
GET/POST /stores/:store_id/products
PUT/DELETE /products/:id
Once you have the routes, you may just first load the parent store, and use the products association:
#store = Store.find(params[:store_id])
#products = store.products
You can scope the products by the store id.
class ProductsController < ApiController
def index
store = Store.find(params[:store_id])
respond_with store.products
end
end
If you look at your route:
http://localhost:3000/api/v1/stores/37/products
You'll find that 37 is part of your route provided in your params, probably in :store_id. Check rake routes to be sure.
Related
I'm using Rails and I have two views in bookings (clients and trainers). I'm trying to route from index to client or trainers depending on who is the current_user.
If I'm a client, route index to trainers
If I'm a trainer, route index to clients
class BookingsController < ApplicationController
def index
end
def clients
#bookings = Booking.where(client: current_user)
#clients = current_user.bookings.map(&:client)
end
def trainers
#trainers = current_user.bookings.map(&:user)
end
end
Rails.application.routes.draw do
resources :bookings do
collection do
get "/clients", to: "bookings#clients", as: "clients"
get "/trainers", to: "bookings#trainers", as: "trainers"
end
resources :shared_training_plans, except: [:new]
end
end
You're doing nesting backwards. And the output of your routes should actually look more like:
GET /instructors/1/bookings # bookings belonging to instructor 1
GET /instructors/1/clients # clients that are associated with instructor 1
GET /students/3/bookings # bookings belonging to student 3
GET /students/3/instructors # instructors belonging to student 3
This describes RESTfully that the endpoint returns something belonging to the other resource. This should be handled by the index action - ideally of a controller designated for that purpose as each controller should only be responsible for one resource.
You could define these routes as:
resources :instructors do
resources :bookings, module: :instructors
end
resources :clients do
resources :bookings, module: :clients
end
module Instructors
class ClientsController < ApplicationController
# GET /instructors/1/clients
def index
#instructor = Instructor.find(params[:instructor_id])
#clients = #instructor.clients
end
end
end
module Clients
class InstructorsController < ApplicationController
# GET /clients/1/clients
def index
#client = Client.find(params[:client_id])
#clients = #client.instrucors
end
end
end
You can implement this by reimagining the feature so that you link the "specific users URL" instead of a generic URL. You can see this for example on the dashboard here on Stackoverflow which uses https://stackoverflow.com/users/{id}/{username}.
REST is in theory supposed to be stateless so each path should return the same resource no matter who is requesting it. But like all rules this could be broken and you can set up routes like:
GET /user/bookings # GET bookings for the currently signed in user
Which would return different results for the signed in user which is stateful. user here would be consider a singular resource. This is mostly useful if you need to have a significantly different represention of the resource when viewing "your own" vs "others".
To actually be able to use the "current user" from the routes the authentication system must be implemented as Rack middleware (like Devise is) and not on the controller level as almost all the "reinventing the wheel" tutorials are. Routing is Rails is implemented as Rack middeware that is run before your controller is ever instanciated.
Devise has the authenticated routing helper which lets you setup different routes for authenticated/unauthenticated users. For example if instructors are implemented as different warden scopes:
authenticated :instructor do
resource :bookings, controller: 'instructors/bookings', only: [:index]
end
authenticated :student do
resource :bookings, controller: 'students/bookings', only: [:index]
end
But you could also use a lambda if you only have one warden scope (the typical Devise configuration):
authenticate :user, ->{|u| u.instructor? } do
resource :bookings, controller: 'instructors/bookings', only: [:index]
end
authenticate :user, ->{|u| u.student? } do
resource :bookings, controller: 'students/bookings', only: [:index]
end
Both of these would route GET /bookings to Instructors::BookingsController#index and Students::BookingsController#indexdepending on which "role" the user has.
I have created API in rails 5.
I am getting error superclass mismatch for class UsersController.
My controller:
module Api
module V1
class UsersController < ApplicationController
def index
users = User.Order('created_at DESC')
render json: {status:"SUCCESS", message:"Load Users", data:users}, status: :ok
end
def create
user = User.new(user_params)
end
def user_params
params.permit(:firstname, :lastname, :email, :age, :id)
end
end
end
end
My routes:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :users
end
end
end
Here is my folder structure:
I got below error in console:
Actually, I have just started in ruby on rails. I tried to figure out problem but couldn't find it.
You need to reference ApplicationController from the Main module (the "global" namespace in Ruby):
module Api
module V1
class UsersController < ::ApplicationController
:: tells Ruby to resolve the constant from main rather than the current module nesting which is Api::V1.
You also need to ensure that ApplicationController inherits from ActionController::API.
See:
Everything you ever wanted to know about constant lookup in Ruby
I'm following Agile Web Development with Rails 5.1 book. And I'm trying to upload a picture file. The example in the book doesn't go into the routes.rb in this section, and I'm clearly not getting this part correct, as when loading the browser I get this error:
Routing Error - undefined local variable or method 'applicationController' for main:Object
routes.rb
Rails.application.routes.draw do
resources :orders
resources :line_items
resources :carts
resources :pictures
root 'store#index', as: 'store_index'
resources :products do
get :who_bought, on: :member
end
get 'upload/new', to: 'upload#get'
end
I have been trying other variations, but always end up with no route matches error:
get 'upload/new', to: 'upload#new' or get 'upload/get', to: 'upload#get'
upload_controller.rb
class UploadController < applicationController
def get //the book uses method 'get', would normally expect 'new'
#picture = Picture.new
end
def picture
#picture = Picture.find(params[:id])
send_data(#picture.data, filename: #picture.name, type: #picture.content_type, disposition: "inline")
end
def save
#picture = Picture.new(picture_params)
if #picture.save
redirect_to(action: 'show', id: #picture.id)
else
render(action: :get)
end
end
def show
#picture = Picture.find(params[:id])
end
private
def picture_params
params.require(:picture).permit(:comment, :upload_picture)
end
end
model/picture.rb
Don't expect the model to be causing an issue with the routes, but adding it for completeness.
class Picture < ActiveRecord::Base
validates_format_of :content_type, with: /|Aimage/, message: "must be a picture"
def uploaded_picture=(picture_field)
self.name = base_part_of(picture_field.original_filename)
self.content_type = picture_field.content_type.chomp
self.data = picture_field.read
end
def base_part_of(file_name)
File.basement(file_name).gsub(/[^|w._-]/, '')
end
end
The issue here is the naming convention that is going wrong. The controller that you are inheriting from is ApplicationController and not applicationController According to the naming convention of rails controller's name is always CamelCase. For more naming convention have a look at this link
Here you need to replace class UploadController < applicationController to class UploadController < ApplicationController
Your writing is incorrect applicationController change to like below
class UploadController < ApplicationController
application to Application
That's it, hope it helps
Im trying to create simple Ruby on Rails REST API.
app/controllers/api/vi/product_controller.rb
module Api
module V1
class ProductController < ApplicationController::API
def index
render json: {message: 'Welcome!'}
end
end
end
end
config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
get '/product', to: 'product_controller#index', as: 'product'
end
end
end
When I run project on localhost, I get uninitialized constant Api::V1::ApplicationController routing error. Can anyone help to such Ruby on Rails newbie as I am?
you just need to create a folder inside controllers called api and a v1 folder inside api.
You should provide all the controllers inside v1 folder.
In your app/controllers/api/v1/product_controller.rb
class Api::V1::ProductController < ApplicationController
def index
render json: {message: 'Welcome!'}
end
end
In your routes:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
get '/product', to: 'product_controller#index', as: 'product'
end
end
end
you nested the route, so it should be '/api/v1/product`
if you run rake routes from your console, you will get a list of all available routes.
for more information about routing and nested routes, have a look at rails guides
change this and try:
module Api
module V1
class ProductController < ApplicationController
def index
render json: {message: 'Welcome!'}
end
end
end
end
I want to create a method that, when called from a controller, will add a nested resource route with a given name that routes to a specific controller. For instance, this...
class Api::V1::FooController < ApplicationController
has_users_route
end
...should be equivalent to...
namespace :api do
namespace :v1 do
resources :foo do
resources :users, controller: 'api_security'
end
end
end
...which would allow them to browse to /api/v1/foo/:foo_id/users and would send requests to the ApiSecurityController. Or would it go to Api::V1::ApiSecurityController? It frankly doesn't matter since they're all in the same namespace. I want to do it this way because I want to avoid having dozens of lines of this:
resources :foo do
resources :users, controller: 'api_security'
end
resources :bar do
resources :users, controller: 'api_security'
end
Using a method is easier to setup and maintain.
I'm fine as far as knowing what to do once the request gets to the controller, but it's the automatic creation of routes that I'm a little unsure of. What's the best way of handling this? The closest I've been able to find is a lot of discussion about engines but that doesn't feel appropriate because this isn't separate functionality that I want to add to my app, it's just dynamic routes that add on to existing resources.
Advice is appreciated!
I ended up building on the blog post suggested by #juanpastas, http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4, and tailoring it to my needs. Calling a method from the controllers ended up being a bad way to handle it. I wrote about the whole thing in my blog at http://blog.subvertallmedia.com/2014/10/08/dynamically-adding-nested-resource-routes-in-rails/ but the TL;DR:
# First draft, "just-make-it-work" code
# app/controllers/concerns/user_authorization.rb
module UserAuthorization
extend ActiveSupport::Concern
module ClassMethods
def register_new_resource(controller_name)
AppName::Application.routes.draw do
puts "Adding #{controller_name}"
namespace :api do
namespace :v1 do
resources controller_name.to_sym do
resources :users, controller: 'user_security', param: :given_id
end
end
end
end
end
end
end
# application_controller.rb
include UserAuthorization
# in routes.rb
['resource1', 'resource2', 'resource3'].each { |resource| ApplicationController.register_new_resource(resource) }
# app/controllers/api/v1/user_security_controller.rb
class Api::V1::UserSecurityController < ApplicationController
before_action :authenticate_user!
before_action :target_id
def index
end
def show
end
private
attr_reader :root_resource
def target_id
# to get around `params[:mystery_resource_id_name]`
#target_id ||= get_target_id
end
def get_target_id
#root_resource = request.fullpath.split('/')[3].singularize
params["#{root_resource}_id".to_sym]
end
def target_model
#target_model ||= root_resource.capitalize.constantize
end
def given_id
params[:given_id]
end
end