Rails 5: Clearance - User CRUD? - ruby-on-rails

I am currently using https://github.com/thoughtbot/clearance
for authentication.
It allows me to sign-up & sign-in using password and email.
But I was wondering how I can configure it to have a CRUD pages for the generated users model, because I actually want to see a list of registered users.

You can use a regular Users controller, subclassed from clearance.
class UsersController < Clearance::UsersController
def index
#logged_in_users = User.where(blah) #whatever logic you need to retrieve the list of users
end
end
I created my Users controller first, then ran the clearance generator, and then the routes generator. After generating the default routes, you can modify to point to your own controller.
rails g clearance:install
rails g clearance:routes
resources :users, controller: "users" do
resource :password,
controller: "clearance/passwords",
only: [:create, :edit, :update]
end
get "/sign_in" => "clearance/sessions#new", as: "sign_in"
delete "/sign_out" => "clearance/sessions#destroy", as: "sign_out"
get "/sign_up" => "clearance/users#new", as: "sign_up"

Related

Route devise to different controller/view set

I'm trying to achieve the following:
I have a simple page where a visitor can view a list of albums (index action) and by clicking on any of the albums, view the photos of every album (show action). No other actions (edit, new, update, destroy) should be reachable.
When the user logs in she can see the index and show pages, plus all the other actions should be available, but now index and show will lock differently. The URLs shouldn't show up any different from before, though.
I created these routes:
users_albums GET /users/albums(.:format) users/albums#index
users_album GET /users/albums/:id(.:format) users/albums#show
new_users_album GET /users/albums/new(.:format) users/albums#new
.... and so on ...
albums GET /albums(.:format) albums#index
album GET /albums/:id(.:format) albums#show
I also created user directories under the app/controllers and app/views directories where I placed the namespaced (logged in) user controllers and views.
So now for the visitor (no login) the default controller/views should be used but once the user signs in (using devise) the controller/views under the user directory should take over.
How can I put this redirection in place?
This is my routes.rb so far:
devise_for :users, skip: [:registrations]
as :user do
get "/sign_in" => "devise/sessions#new" # custom path to login/sign_in
get "/sign_up" => "devise/registrations#new", as: "new_user_registration" # custom path to sign_up/registration
get 'users/edit' => 'devise/registrations#edit', :as => 'edit_user_registration'
put 'users' => 'devise/registrations#update', :as => 'user_registration'
end
namespace :users do
resources :albums do
resources :photos
end
end
resources :albums, only: [:index, :show] do
resources :photos, only: [:index, :show]
end
root to: "albums#index"
As I understand you need to redirect users only after login if different views/controllers are used. You can use devise after_sign_in_path_for method. Add it to your application controller:
def after_sign_in_path_for(resource)
users_albums_path #or any route that you want use
end
But for allow/deny actions or show/hide links better approach use something like gem pundit and avoid DRY.

Devise in rails 4 doesn't show sign_in form

I have a problem with the devise gem, I have this controller.
class AdminController < ApplicationController
before_action :authenticate_user!
def index
end
def per
end
def po
end
end
When redirect to sign_in form , shows nothing
sign_in form
These are my routes:
match 'po' => 'admin#po', :via => :get
match 'per' => 'admin#per', :via => :get
match 'admin' => 'admin#index', :via => :get
match 'admin/index' => 'admin#index', :via => :get
match 'admin/per' => 'admin#per', :via => :get
match 'admin/po' => 'admin#po', :via => :get
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }
root 'home#index'
I have three templates: application, admin and home
I overwrite the default route after log in
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
#before_action :authenticate_user!
def after_sign_in_path_for(resource)
#request.env['omniauth.origin'] || stored_location_for(resource) || admin_path
admin_path
end
end
My last gem installed:
gem 'bootstrap-sass'
You need to run the generator for Devise views which will copy the necessary files in your views folder:
Run:
rails g devise:views
There is more information on configuring the Devise views here
Your problem probably isn't with Devise, it looks systemic to me.
#config/routes.rb
namespace :admin do
root "application#index" #->
resources :model_controller, path: "", only: :index do #-> url.com/admin/...
collection do
get :po #-> you shouldn't really have this
get :per #-> you shouldn't really have this
end
end
end
devise_for :users, :controllers => { :omniauth_callbacks => "callbacks" }
This will give you the following:
#app/controllers/admin/application_controller.rb
class Admin::ApplicationController < ApplicationController
before_action :authenticate_user!
def index
# do something here
end
end
This gives you the ability to create a custom "dashboard" type page for your admin area, from which you'll be able to use controllers bound to models.
Your po and per actions really shouldn't be there - they are not part of the CRUD system
In regards to your Devise views, the other answers are correct in that you would be best to generate the Devise views in your app:
rails generate devise:views
This won't solve your problem (hence why I downvoted the other answers). It will simply put the views in your app. It will do nothing apart from put code in a different place.
You will need to debug the issue:
Check the action you're seeing at /users/sign_in
Check the code in the <body> tags (which you haven't shown)
If the HTML is there, there will be some other issue preventing it from loading
If there is no HTML, it will likely mean a problem with the core of Devise
What I would recommend you do is the following:
Generate your views
From your screenshot, show us the contents of the <body> tag
Screenshot your console log (this will show any errors)
Update your question with the above
This will give you a much clearer perspective on what the potential issue will be, and allow other community members to better define the solution.

How to have a rails admin and only 1 admin that can edit & create new posts

I am trying to create a simple page where any visitor can read the weekly posts. I want there to only be 1 admin that can edit or create new posts.
How would I go about creating this?
I started with devise but theoretically anyone can go to the new_user_registration path and create a new user that would have access to the edit & new actions. How would I be able to restrict any new accounts from being created after the one I create? Or ideally limit the actions that any user that is not an admin can use?
I looked into Pundit for authorization but it seems like it is too much for such a simple task, is there a more simple way to do this with Rails?
If you just want to have 1 user, a single administrator, with a login and password, and no other user accounts, then I would recommend HTTP Digest Auth, which is supported by rails out-of-the-box and doesn't require any extra gems or plugins. (Or HTTP Basic Auth, but digest auth is a little more secure.)
Most of the following is taken from the action controller guide on the rails website.
In config/routes.rb:
resources :posts
In controllers/posts_controller.rb:
class PostsController < ActionController::Base
USERS = { "admin" => "password" }
before_action :authenticate, except: [:index, :show]
# actions here (index, show, new, create, edit, update, destroy)
private
def authenticate
authenticate_or_request_with_http_digest do |username|
USERS[username]
end
end
end
If you want, you can modify the routes so that the new/create/edit/update/destroy actions are in the 'admin/' part of the website:
In config/routes.rb:
scope '/admin' do
resources :posts, except: [:index, :show]
end
resources :posts, only: [:index, :show]
This will still direct all post-related requests to the PostsController.
You simply can add a new attribute to your User model that define if the instantiated user is an admin or not, for example. Let's call this attribute isAdmin
An in your edit controller you can do the following:
if user.isAdmin==true
# your edition code here
else
#redirect
end

Changing user params to include their username

To view a user page on my app you have to enter their id /user/2.
How can I make it so that it uses their username in the params instead of id value for user show page? I would like it to be /user/username or /username.
Any help would be appreciated. Thanks!
Routes:
get 'signup' => 'users#new'
get 'login' => 'sessions#new'
get 'logout' => 'sessions#destroy'
get 'edit' => 'users#edit'
get "/profile/:id" => "users#show"
get "profile/:id/settings" => 'users#edit'
get 'settings/:id' => 'users#settings'
resources :users do
resources :messages do
post :new
collection do
get :askout
end
end
collection do
get :trashbin
post :empty_trash
end
end
Users controller:
def show
#user = User.find(params[:id])
end
In my experience the easiest way I've found to do this is to use the friendly_id gem. There is a to_param method in ActiveRecord that you can set to define what a model's route id is going to be, but if the attribute you want to use is not already URL friendly it will become very complicated. Assuming your usernames contain no spaces or other URL "unfriendly" characters, you could do something like this:
class User < ActiveRecord::Base
...
def to_param
username
end
...
end
But make sure in your controller you then find users by their username.
class UsersController < ApplicationController
...
def set_user
#user = User.find_by(username: params[:id])
end
end
Note that if the value in to_param is not EXACTLY what the value in the database is, finding your object again is going to be more difficult. For example, if you wanted to use name.parameterize to set have URLs like /users/john-doe when your actual name attribute is John Doe, you'll have to find a way to consistently "deparameterize" your parameter. Or, you can create a new database column that contains a unique parameterized string of the attribute you want in the url (called a slug). That's why I use friendly_id. It handles your slugs semi-automatically.
As for routing your users to /username, you have several options:
get ':id', to: 'users#show', as: 'show'
resources 'users', path: '/'
Just make sure you put these routes at the end of your routes file. That way if you try to get to your index action on SomeOtherModelsController by going to /some_other_model it's not going to try to find you a user with username "some_other_model".
There are two options:
i. You can use the gem friendly_id
ii. Add to_param method to your user model and return username
class User < ActiveRecord::Base
def to_param
username
end
end
With this option you will have to replace
User.find(params[:id])
with
User.find_by_username(params[:id])
Slugged Routes
What you're asking about is something called "slugged" routes.
These are when you use a slug in your application to determine which objects to load, rather than using a primary_key (usually an id)
To handle this in Rails, you'll need to be able to support the slug in the backend, and the best way to do this is to use the friendly_id gem:
friendly_id
Id highly recommend using the friendly_id gem for this:
#app/models/user.rb
Class User < ActiveRecord::Base
friendly_id :username, use: [:slugged, :finders]
end
The friendly_id gem does 2 things extremely well:
It "upgrades" the ActiveRecord find method to use the slug column, as well as the primary key
It allows you to reference the slugged object directly in your link helpers
It basically means you can do this:
<%= link_to user.name, user %>
If using the friendly_id gem, this will automatically populate with the slug attribute of your table
Further, it allows you to do something else - it gives you the ability to treat the params[:id] option in the backend in exactly the same way as before - providing the functionality you require.
Routes
You should clear up your routes as follows:
#config/routes.rb
get "/profile/:id" => "users#show"
get "profile/:id/settings" => 'users#edit'
get 'settings/:id' => 'users#settings'
resources :sessions, only: [:new, :destroy], path_names: { new: "login", destroy: "logout" }
resources :users, path_names: { new: "signup" } do
resources :messages do
post :new
collection do
get :askout
end
end
collection do
get :trashbin
post :empty_trash
end
end
Make a new attribute on your User model. You can call it what you want, but usually it's called "slug".
rails g migration AddSlugToUser slug:string
rake db:migrate
Store in it a "url friendly" version of the username. the parameterize method is good for that.
class User < ActiveRecord::Base
before_save :create_slug
def create_slug
self.slug = self.name.parameterize
end
In the same model create a to_param method which will automatically include slug in links (instead of the user id)
def to_param
slug
end
Finally, where you do User.find replace it with find_by_slug
#user = User.find_by_slug(params[:id])

Rails: Point several nested routes to one customer controller action

How do you point different nested routes to one controller action?
A user can be a member of several groups like company, project, group ect. for which It can request to join, leave or be removed by an admin.
I want to access the remove action for several models and destroy the belongs_to record in the profile model
I already have a polymorphic model that takes requests from a profile to a model( e.g. company) and upon acceptance of the request the profile will belong to the model. once the request is accepted the request recored is destroyed. I feel that the remove action that will destroy the relationship between the profile and the model should be part of the requests_controller, but I guess could be part of the profile_controller.
What I'm thinking I need to end up with is either
/_model_/:id/profile/:id/remove
/company/:id/profile/:id/remove
but how do I get this to point the remove action in my requests controller
or
/_model_/:id/requests/remove
/company/:id/request/remove
I am using the following code in my routes
resources :companies do
resource :requests do
put 'remove', :on => :member
end
resources :requests do
put 'accept', :on => :member
end
end
This is producing a double up of the routes
remove_company_requests PUT /companies/:company_id/requests/remove(.:format)
company_requests POST /companies/:company_id/requests(.:format)
new_company_requests GET /companies/:company_id/requests/new(.:format)
edit_company_requests GET /companies/:company_id/requests/edit(.:format)
GET /companies/:company_id/requests(.:format)
PUT /companies/:company_id/requests(.:format)
DELETE /companies/:company_id/requests(.:format)
accept_company_request PUT /companies/:company_id/requests/:id/accept(.:format)
GET /companies/:company_id/requests(.:format)
POST /companies/:company_id/requests(.:format)
new_company_request GET /companies/:company_id/requests/new(.:format)
edit_company_request GET /companies/:company_id/requests/:id/edit(.:format)
company_request GET /companies/:company_id/requests/:id(.:format)
PUT /companies/:company_id/requests/:id(.:format)
DELETE /companies/:company_id/requests/:id(.:format)
As
My I suggest that you create a new controller to handle this? The advantage is that you can map the route to this controller on any models you want the "remove association" on.
For example:
# RemoveController.rb
class RemoveController < ApplicationController
def destroy
# inplement the logic for deletion. You can use refection to implement
# this function only once for all the applied associations.
end
end
# routes.rb
resources :companies do
resource :requests do
resource :remove, :controller => :remove, :only => [:destroy]
end
end
The above routes would generate:
company_requests_remove DELETE /companies/:company_id/requests/remove(.:format) remove#destroy
You can nest the above line for the remove controller on any nested routes you want and they will all point back to the RemoteController's destroy object, only with different parameters to help you implement the destroy action.
Edit: to add create for specific relationship that you don't want to duplicate you can do this:
# routes.rb
resources :companies do
resource :requests do
resource :remove, :controller => :relationship, :only => [:destroy]
resource :create, :controller => :relationship, :only => [:create]
end
end
company_requests_remove DELETE /companies/:company_id/requests/remove(.:format) relationship#destroy
company_requests_create POST /companies/:company_id/requests/create(.:format) relationship#create
But I think you might need to be careful about breaking the convention of create in the respective controller. I'm not sure if there are any downside to this. The remove part since is only removing association and not the records itself, it doesn't seem to break the convention.
Try
puts 'remove', :on => :member, :controller => :requests, :action => :remove

Resources