I have routes like this. I have nested batches routes with streams.
resources :batches
resources :streams do
resources :batches do
collection do
post 'create_batch'
end
end
end
When i go to the link.
create_batch_stream_batches_path(stream)
In bacthesControlller.rb it calls for before action :set_batch which it should not as the before_action
before_action :set_batch, only: [:show, :edit, :update, :destroy]
def create_batch
logger.info params
binding.pry
end
I tried with skip_before_action for create_batch but it didn't work.
In your nested route, you can simply pass a custom parameter, which will be passed on to the controller during a request:
resources :batches
resources :streams do
resources :batches, skip: true do
end
end
end
In the controller you check whether the parameter is passed, and don't run the hook if the flag is set:
before_action :set_batch, unless: -> { params[:skip] }
Related
I am creating a Task Manager.
So, I have:
A dashboard where I can do CRUD for tasks (and then I will assign them a group)
A group section where I can go inside a group and do CRUD for tasks
Then my routes would look something like this:
Rails.application.routes.draw do
devise_for :users
namespace :api, defaults: { format: :json } do
namespace :v1 do
resources :groups, only: [ :index, :show, :create, :update, :destroy] do
member do
resources :tasks, only: [:index, :show, :create, :update, :destroy]
end
end
resources :tasks, only: [:index, :show, :create, :update, :destroy]
end
end
end
For this:
A dashboard where I can do CRUD for tasks (and then I will assign them a group)
resources :tasks, only: [:index, :show, :create, :update, :destroy]
And for this:
A group section where I can go inside a group and do CRUD for tasks
resources :groups, only: [ :index, :show, :create, :update, :destroy] do
member do
resources :tasks, only: [:index, :show, :create, :update, :destroy]
end
end
But I feel this is duplicating routes. I have 2 questions:
Is this the right approach or would there be a better way to solve it?
If I go with this, how would I approach the controller actions? Since in one situation (2) we would get the params[:group_id] but in the other I would need to add it as strong_params. Would an if-else condition work? (if there is no [:group_id] in strong_params then take the params[:group_id])
Thanks!
Is this the right approach or would there be a better way to solve it?
No. Not really.
Deeply nested routes quickly become cumbersome to work with so you should consider using shallow nesting. This only nests the collection routes (index, new, create) and not the member routes (show, edit, update, destroy).
If tasks have a unique id you should be able to show, edit, update and destroy them without the group being involved. The exception to this rule is if tasks are only unique per group (but why?).
In an API this is especially true. The backend shouldn't need to know that you're modifying the resource from page X or Y in your frontend application.
You can use the shallow: true option to generate shallow routes but I would probally just define it as:
# This respresents tasks not nested in a group
resources :tasks, only: [:index, :show, :create, :update, :destroy]
resources :groups, only: [:index, :show, :create, :update, :destroy] do
resources :tasks, only: [:index, :create]
end
If I go with this, how would I approach the controller actions?
There are really two different approaches here. One I call the "param sniffing method":
module API
module V1
class TasksController
# GET /api/v1/groups/1/tasks
# GET /api/v1/tasks
def index
if params[:group_id]
render json: Group.find(params[:group_id]).tasks
else
render json: Task.all
end
end
end
end
end
This is the most obvious solution but blatantly violates the Single Responsibity Principle and increases the cyclic complexity of all the methods.
The other solution is to route the nested representation of the resource to a separate controller:
# This respresents tasks not nested in a group
resources :tasks, only: [:index, :show, :create, :update, :destroy]
resources :groups, only: [ :index, :show, :create, :update, :destroy] do
resources :tasks, only: [:index, :create], module: :groups
end
This routes the /groups/:group_id/tasks routes into API::V1::Groups::TasksController.
module API
module V1
class TasksController
# GET /api/v1/tasks
def index
render json: Task.all
end
end
end
end
module API
module V1
module Groups
class TasksController
before_action :set_group
# GET /api/v1/groups/1/tasks
def index
render json: #group.tasks
end
private
def set_group
#group = Group.find(params[:group_id])
end
end
end
end
end
You could also just do resources :tasks, only: [:index, :create], controller: :group_tasks if you want to avoid nesting the contants one more step.
as I am trying to create web with subodmain for every user i coded as below:
In below controller i am checking if subdomain from sign_in devise page belons to user in User.rb table. If not i am logging out.
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :check_domain
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:slug])
devise_parameter_sanitizer.permit(:account_update, keys: [:slug])
end
def check_domain
unless current_user.nil?
#domain = User.find_by(email: current_user.email)
if #domain.slug != request.subdomain
sign_out_and_redirect(current_user)
flash.alert = "User not found."
end
end
end
end
At this moment I have routes.rb as below:
Rails.application.routes.draw do
resources :devise
resources :places
resources :people_items
resources :people, only: [:edit, :update]
resources :gift_items
resources :gifts, only: [:edit, :update]
resources :program_items
resources :story_items
devise_for :users
root to: 'pages#index'
get '/home', to: 'output#home'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
resources :pages do
member do
delete :delete_file
end
end
resources :stories, only: [:edit, :update]
resources :programs, only: [:edit, :update]
resources :galleries, only: [:edit, :update] do
member do
delete :delete_image
end
end
get 'informations/information'
resources :aboutus, only: [:edit, :update]
end
I modified one more settings in config/environments/development.rb
config.action_dispatch.tld_length = 1
Everything is working fine.
In tutorials i found out that for working subdomains properly I should modify routes.rb as below. But what is the purpose of modification file below? Do I really need to touch routes.rb? That's my question.
constraints subdomain: /.*/ do
resources :pages
end
Do I really need to modify this? Or what is purpose of this modification? Is that really necessary?
Thank you so much for your advices!
Constraints in routes.rb allow you to restrict when routes should or shouldn't be matched, returning a 404 in the event that they do not.
You can do this on a per-route basis for params like so:
get 'products/:category', to: "products#index", category: /(fruit|electronics|medicine)/, as: :products
This would allow requests to http://yourdomain.com/products/fruit but not to http://yourdomain.com/products/vehicles for example. Accessing any forbidden domains would provide you with a 404 response.
You can provide generic constraints to a bunch of different routes by enclosing them in a constraints block, like so:
constraints ->(req) { Site.exists?(subdomain: req.subdomain) } do
resources :articles, path: "blog", only: [:index, :show]
...
end
So here, we're checking that a Site record exists with the subdomain provided. If not, we return a 404.
I have a weird error when I want to redirect users to the root_url when they try to access blogs/new url in my app.
My routes are
resources :blogs, only: [:index, :show] do
resources :comments, only: [:create]
end
namespace :admin do
resources :blogs
resources :users, only: [:index, :show]
resources :comments, only: [:create, :new, :destroy]
end
My non-admin blogs controller looks like this:
class BlogsController < ApplicationController
before_action :set_blog, only: [:show]
def show
unless #blog
redirect_to blogs_path
flash[:notice] = "You are not authorized to create a post."
end
end
def index
#blogs = Blog.all
end
private
def set_blog
#blog = Blog.find(params[:id])
end
end
I get the error Couldn't find Blog with 'id'=new.
In rails, the priority of routes goes from top to bottom. Meaning, when you try to hit /blogs/new, the route gets matched with the show action of blogs defined in the top of your routes.rb.
blogs/new gets matched with /blogs/:id which is mapped to blogs#show action.
In the set_blog method, params[:id] is new and since there is no record with the id of new, you're getting that weird error.
How to get around this? Change the priority of your routes.
Move the following block below the admin namespaced routes.
namespace :admin do
resources :blogs
resources :users, only: [:index, :show]
resources :comments, only: [:create, :new, :destroy]
end
resources :blogs, only: [:index, :show] do
resources :comments, only: [:create]
end
By the way, your question says that you want to avoid non-admin users to access blogs#new. If that's the case, you should try to hit /admin/blogs/new and not /blogs/new.
If you had done that, you wouldn't have gotten the error in the first place. But still, its good to know about the priority of routes in rails.
Hope this helps!
I'm having troubles with routes in Ruby on Rails. I've configured routes this way
resources :users do
collection do
resource :registrations, only: [:show, :create]
resource :sessions, only: [:new, :create, :destroy]
resource :confirmations, only: [:show]
end
end
And I have a RegistrationsController where I have two endpoints (new, create)
class RegistrationsController < ApplicationController
skip_before_filter :authenticate!
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = t("registrations.user.success")
redirect_to :root
end
end
end
But when I do rails s and I put localhost:3000/users/registrations/create or new I get a "no route matches". And I think the route exist because If I do raake routes I get this
registrations POST /users/registrations(.:format) registrations#create
GET /users/registrations(.:format) registrations#show
I know it should be a silly mistake but I don't get it. I appreciate any help
When you define routes for registrations, you're limiting it to just [:show, :create]:
resource :registrations, only: [:show, :create]
But your controller (correctly!) is presuming that there are two routes: new (to show the registration form) and create (to create the new user). You need to change your routes so that they match your controller actions:
resources :users do
collection do
resource :registrations, only: [:new, :create] # Updated this line!
resource :sessions, only: [:new, :create, :destroy]
resource :confirmations, only: [:show]
end
end
I am adding tagging functionality to my app, via acts_as_taggable_on.
That gem doesn't add controllers, but I would like to. I am adding the tagging functionality to my Node model.
On my NodeController, I know I could simply add the explicit actions like this:
def add_tagged_user
end
def remove_tagged_user
end
def tagged_users
end
But that doesn't feel very restful or Railsy.
The corresponding route would look like this:
resources :nodes do
match :add_tagged_user, via: [:post], on: :member
match :remove_tagged_user, via: [:delete], on: :member
match :tagged_users, via: [:get], on: :member
end
Is there a RESTful or a more Railsy way to do this?
You could go with a single TagsController, with routes that match the RESTful resource(s).
Something like
Routes
# routes.rb
resources :nodes do
resources :tags, only: [:show, :create, :update]
end
resources :other_resources do
resources :tags, only: [:show, :create, :update]
end
Controller
class TagsController < ApplicationController
before_action :load_taggable
def create
#taggable.tags.create(tag_params)
end
private
def load_taggable
# switches on params
#taggable = if params[:node_id]
Node.find(params[:node_id]
elsif # other things that are taggable
# OtherThing.find(...)
end
end
end