I'm looking for some feedback on both the philosophy of good practice and technical practice of using a top level route that routes to multiple Models based on conditions.
I need to have a top level route domain.com/:id that either routes to a: Company or User.
The condition/identifier being that a User has an # in the url, e.g. domain.com/#theminijohn
My routes for the moment look like this:
devise_for :users, path: '',
path_names: {
sign_up: '',
registration: 'signup',
sign_in: 'login',
password: 'password',
confirmation: 'verification'
},
controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations',
omniauth_callbacks: 'users/omniauth_callbacks',
passwords: 'users/passwords'
}
resources :users, path: '', only: [:show] do
member do
get 'reviews', to: 'users#reviews', as: :reviews
get :following, :followers
post :follow, to: 'users#follow_user'
post :unfollow, to: 'users#unfollow_user'
end
end
resources :companies, path: '', only: [:show], as: :company do
resources :products, path: '', only: [:show], as: :product
end
Furthermore the # sign will only be used in the url, aka it is not present in the attribute.
How do I go about this ?
Edit: Here's where I am:
the constraint that gets called from the route for the :users resource
module Constraints
class UserProfile
def matches?(request)
if request.path.include?('#')
slug = request.path.delete('/#')
User.where(slug: slug).exists?
end
end
end
end
and in the controller I patched the find method to:
def set_user
#user = User.includes(:reviews).find(params[:id].delete('#'))
end
How I ended up solving this:
1) Include the # in the friendly_id slug
To do this I had to patch the normalize function which strips it out when calling .parameterize
# overwrite normalize function because it's stripping
# out '#' when calling .parameterize
def normalize_friendly_id(value)
"#" + value.to_s.parameterize
end
2) Regenerate all slugs
There are various methods, I went with deleting the slug and generating it again.
User.each do |u|
u.update_attribute(:slug, nil)
end
User.find_each(&:save)
Have in mind should_generate_new_friendly_id? & if you overwrote it.
3) Routing Constraint
I wrapped my User routes in a constraint:
constraints(Constraints::UserProfile.new) do
resources :users,
....
end
which looks like this:
module Constraints
class UserProfile
def matches?(request)
if request.path.match? /\/#(.*)/
slug = request.path.split('/')[1]
User.where(slug: slug).exists?
end
end
end
end
VoilĂ .
Related
I'm overriding Devise controllers in Rails like that:
module Api
module V1
module Devise
class RegistrationsController < Devise::RegistrationsController
....
end
end
end
end
And thats my routes:
Rails.application.routes.draw do
root 'application#index'
devise_for :users
devise_for :taxi_drivers, only: :passwords
resources :taxi_drivers_groups, only: %i[index new create edit update]
namespace :api do
namespace :v1 do
devise_for :users,
defaults: { format: :json },
class_name: 'User',
skip: %i[registrations sessions passwords],
path: '',
path_names: { sign_in: 'login', sign_out: 'logout' }
devise_scope :user do
post 'signup', to: 'devise/registrations#create'
post 'login', to:'devise/sessions#create'
delete 'logout', to: 'devise/sessions#destroy'
post 'password_recover', to: 'devise/passwords#create'
put 'password_recover', to: 'devise/passwords#update'
end
end
end
end
And I'm getting an error when I try to pass my tests:
ActionController::RoutingError: uninitialized constant Api::V1::Devise::RegistrationsController
And in my test file:
test 'user invalid sigup with empty fields' do
#valid_signup_params[:name] = nil
post('/api/v1/signup.json', { user: #valid_signup_params }, headers: { format: :json })
assert_equal last_response.status, 422
assert_equal json_response['errors']['name'].count, 1
end
Do you have any idea how to fix this error?
Thanks!
The issues comes from a constant clash between the newly defined controller and the existing one.
Replacing class RegistrationsController < Devise::RegistrationsController with class RegistrationsController < ::Devise::RegistrationsController is fixing the issue as ruby knows that he has to find the RegistrationsController class in the previously defined Devise module and not in the current definition.
First, see my routes :
Rails.application.routes.draw do
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'
devise_for :users, controllers: {
registrations: "registrations",
sessions: "sessions"
}
devise_scope :user do
authenticated :user do
root 'appointments#index', as: :authenticated_root
end
unauthenticated do
root 'sessions#new', as: :unauthenticated_root
end
match '/logout', :to => 'devise/sessions#destroy', via: :all
end
resources :appointments do
get :available_slots, on: :collection
resources :notes
resources :images, only: [:show]
end
#patch 'appointments/:id' => "appointments#update_status", as: :update_status
match 'appointments/:id/update_status' => "appointments#update_status", :via => :post
match 'appointments/:id/visited_patient_appointment' => "appointments#visited_patient_appointment", :via => :post
get 'archive' => "appointments#archive"
end
Now, how to redirect to appointments_path after user sign in? There is one devise method called after_sign_in_path_for(resource) which I override in Appointments Controller but still it is not working.
You trying to override in Appointments Controller which is wrong, it will be sessions_controller.rb or application_controller.rb
Try the following in the sessions_controller.rb or application_controller.rb
protected
def after_sign_in_path_for(resource)
stored_location_for(resource) || appointments_path
end
If not have stored_location then he will redirect to appointments_path
If you need to redirect all time to the same page like appointments_path then
protected
def after_sign_in_path_for(resource)
appointments_path
end
See the Devise wiki
I have a controller pages#dashboard that I have set to be the user_root in my routes.rb
devise_for :users, controllers: { registrations: 'registrations' }
devise_scope :user do
get "/signup" => "devise/registrations#new"
get "/login" => "devise/sessions#new"
get "/logout" => "devise/sessions#destroy"
get "/:id" => "users#dashboard", :as => "user_root", :id => :username
end
I want the URL to be http://my_app.com/current_user_username
I have set the to_param in the user.rb model to:
def to_param
username
end
This works fine when I have a link such as:
<%= link_to "MY ACCOUNT", user_root_path(current_user) %>
But on initial login, the URL is: http://my_app.com/username (explicitly says 'username') instead of http://my_app.com/current_user_username
I have tried setting my to_param to:
def to_param
"#{username}"
end
and have also tried setting my route.rb to:
get "/:id" => "users#dashboard", :as => "user_root", :id => :username, :via => :get
Neither of these have solved the issue.
Environment: Rails 4.2.5, Ruby 2.3.0, Devise gem 3.5.3
Any help would be greatly appreciated.
You can simply add another route above everything in your routes.rb as:
get "/:username" => "users#dashboard", :as => "user_root"
devise_for :users, controllers: { registrations: 'registrations' }
devise_scope :user do
get "/signup" => "devise/registrations#new"
get "/login" => "devise/sessions#new"
get "/logout" => "devise/sessions#destroy"
get "/:id" => "users#dashboard", :as => "user_root", :id => :username
end
Then you need to find the user by something like this:
#user = User.find_by(username: params[:username])
Just use friendly_id:
#Gemfile
gem 'friendly_id', '~> 5.1'
$ rails generate friendly_id #-> generates initializer
$ rails generate scaffold user name:string slug:string:uniq #-> adds slug column
$ rake db:migrate
#app/models/user.rb
class User < ActiveRecord::Base
extend FriendlyId
friendly_id :username, use: [:slugged, :finders]
end
This will allow you to use the slug / username in lieu of the primary key:
<%= link_to current_user.username, user_path(current_user) #-> <a href="/users/john_jones" %>
You would not have to change your controller code etc for this to work.
--
Your routes can be simplified:
#config/routes.rb
resource :user, path: "", only: :show #-> url.com/:id to users#show
devise_for :users, controllers: { registrations: 'registrations' }, path_names: { sign_in: "login", password: "forgot", confirmation: "confirm", unlock: "unblock", sign_up: "", registration: "signup", sign_out: "logout" }
Whilst I don't know if you can use current_user inside your routes (I'm sure you can but I've not tested), you may wish to use /profile like this:
resource :profile, controller: :users, only: :show
This will send url.com/profile to the users#show action:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
current_user...
end
end
I solved this by adding this to application_controller.rb
def after_sign_in_path_for(resource)
user_root_path(current_user)
end
** This is a Devise specific answer.
Thanks to everyone who took the time to answer :)
I had a similar problem and made a few changes to routes and it worked here are the sets to do:
under User.rb model
def to_param
username
end
Routes:
resources :users, :path => '/' do
end
Controllers
#user=User.find_by_username(params[:id])
Views
link_to #user.username, #user.username.
Avoid user_path because it would redirect you to www.example.com/users/david
instead follow up this i have noted above
it will www.example.com/david this is better than the first.
hope anyone would be helped.
I'd like to use something like domain.com/users/foo instead of the routes looking like /users/user_id/
Fair enough you could do get "/users/:name/..... but I am using nested resource:
devise_for :users, :controllers => { omniauth_callbacks: 'omniauth_callbacks' }
resources :users do
resource :profile
resources :supports do
post :interest
end
end
Is there a way to change the resource users/user_id to users/firstname?
for this you can use friendly_id gem or you can define to_param method in your user model.
def to_param
"#{name}"
end
I am using Devise 3.1.1 and am trying to redirect user to the Sign In page after he signs up.
As instructed in Devise's wiki I overridden RegistrationsController with the following:
class RegistrationsController < Devise::RegistrationsController
protected
def after_inactive_sign_up_path_for(resource)
'/users/sign_in'
end
end
As instructed, I also added the following line to the routes.rb:
devise_for :users, controllers: { registrations: 'registrations'}
After which I get the following error when I go to sign in page:
Invalid route name, already in use: 'new_user_session' You may have defined two routes with the same name using the:asoption, or you may be overriding a route already defined by a resource with the same naming.
In my routes I already have this defined:
devise_for :users, skip: :registrations
devise_scope :user do
resource :registration,
# disabled :edit & :destroy
only: [:new, :create, :update],
path: 'users',
path_names: { new: 'sign_up' },
controller: 'devise/registrations',
as: :user_registration do
get :cancel
end
end
You can only define the devise_for block once, and as you're already messing with the default registrations controller you should be able to just do something like the following to have devise use your controller:
devise_for :users, skip: :registrations
devise_scope :user do
resource :registration,
# disabled :edit & :destroy
only: [:new, :create, :update],
path: 'users',
path_names: { new: 'sign_up' },
controller: 'registrations',
as: :user_registration do
get :cancel
end
end