I have a Users model that has an admin field. This is boolean. I only want to a User to create/modify/update a "Season" if the admin field is equal to true. How would I go about doing this in rails? Do i put this logic in the "Season" controller? Something to do with my routes config?
You can use single table inheritance like create a table User and create 2 more model like
Admin < User
{
default_scope { where(user_type: :college) }
....
}
and
Supplier < User
{
default_scope { where(user_type: :supplier) }
....
}
Put the logic in the SeasonsController in a before_action, that performs your desired error action unless they are an admin.
class SeasonsController < ApplicationController
before_action :authenticate_user!
before_action :ensure_admin_user!, except: [:show, :index]
...
private
def ensure_admin_user!
render_error unless current_user.admin?
end
def render_error
# whatever
end
end
Action Controller Docs: filters
I suggest you add a before_action filter in your controller:
class SessionsController < ApplicationController
before_action :check_permission!, only: [:create, :update]
# Your create, update actions
private
def check_permission!
render status: :forbidden unless current_user.admin?
end
end
You can create before_action callback to check whether the user's admin field is true or false.
class SeasonsController
before_action :check_admin_field, only: [:edit, :new]
protected
def check_admin_field
return User.find(params[:user_id]).admin_field
# if you're using devise gem then check current_user.admin_field
# or you can redirect to other path with a notice
end
end
You can check this here
If you are not passing the user_id parameter in your URL then you have to change the routes.rb like:
resources :users do
resources :seasons
end
This will create your new season route url like: root_url/users/1/seasons/new
put 'seasons/:user_id/:id' => 'home#update', :constraints => {user_id: User.where(admin: true).pluck(:id) }
post 'seasons/:user_id/:id' => 'home#create', :constraints => {user_id: User.where(admin: true).pluck(:id) }
resources :seasons, except: [:create, :update]
Change your route.rb file as per above.
Related
i have the following method on application_controller:
controllers/application_controller.rb
def set_footer_sponsored_links
#sponsored_links = Rails.cache.fetch("sponsored_links", expires_in: 240.hours) {
ContentGroup.sponsored_links
}
end
This sponsored_links (ContentGroup.sponsored_links) are created and updated via API:
controllers/api/v1/sponsored_links_controller.rb:
class Api::V1::FooterSponsoredLinksController < Api::V1::ApiController
before_filter :set_sponsored_link, only: [:destroy, :update, :show]
def create
sponsored_link = SponsoredLink.create(sponsored_link_params)
respond_with :api, sponsored_link
end
def update
#sponsored_link.update(sponsored_link_params)
respond_with :api, #sponsored_link
end
What i need is to set an expire cache method or similar that, from the API, expires cache on the method on application_controller whenever :create, :update or :destroy are called.
Any idea how to accomplish so?
Thanks in advance
First, create a private method in your Api::V1::FooterSponsoredLinksController called expire_links:
...
private
def expire_links
Rails.cache.delete('sponsored_links')
end
Then add a before_filter for that method like this:
before_filter :expire_links, only: [:create, :update, :destroy]
You should probably use a cache key that includes the maximum updated_at DateTime like so:
def set_footer_sponsored_links
#sponsored_links = Rails.cache.fetch("sponsored_links-#{SponsoredLink.maximum(:updated_at).try(:utc).try(:to_s, :number)}", expires_in: 240.hours) {
ContentGroup.sponsored_links
}
end
I have simply route in my rails application that looks like this:
resources :users, only: :show
And now for example I want to redirect to http://no_present_path.com when user with sended id is not present and when user with seneded id is present redirect to http://present_path.com. Is it any way to do this with routes constraints?
Routes are meant as a simple match between a string representing a part of a url, method and an action within a controller. The best way to achieve what you're after is using a before_action in your controller. Example
class UsersController < ApplicationController
before_action :authenticate_user, only: [:show]
def show
...
end
private
def authenticate_user
redirect_to some_other_path unless id_correct?
end
end
Currently, my Users database has a column called "admin" with a boolean value and the default set to false. I have one admin user seeded into the database.
How do write my application so that users who are the admin can create new users, but users who are not cannot? (Also, users should be created only by the admin)
It seems like there should be a simple way to do this in devise that does not involve using some external module. So far however, I have not been able to find a satisfactory answer.
I would be more likely to mark the solution which is devise only. (One that is simply standard MVC/Rails solution a plus) However, if there really is a better way to do it that doesn't involve CanCan I may accept that too.
NOTE:
I have been searching around for a while and I've found several other stackoverflow questions that are very similar to this one, but either don't quite answer the question, or use other non-devise modules. (Or both)
To implement the authorization, use a method on the controller
Exactly as suggested by #diego.greyrobot
class UsersController < ApplicationController
before_filter :authorize_admin, only: :create
def create
# admins only
end
private
# This should probably be abstracted to ApplicationController
# as shown by diego.greyrobot
def authorize_admin
return unless !current_user.admin?
redirect_to root_path, alert: 'Admins only!'
end
end
To sidestep the Devise 'already logged in' problem, define a new route for creating users.
We will simply define a new route to handle the creation of users and then point the form to that location. This way, the form submission does not pass through the devise controller so you can feel free to use it anywhere you want in the normal Rails way.
# routes.rb
Rails.application.routes.draw do
devise_for :users
resources :users, except: :create
# Name it however you want
post 'create_user' => 'users#create', as: :create_user
end
# users/new.html.erb
# notice the url argument
<%= form_for User.new, url: create_user_path do |f| %>
# The form content
<% end %>
This seems like the simplist approach. It just requires sub-classing the devise controller. See the docs for how to do that.
# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
before_action :authenticate_user!, :redirect_unless_admin, only: [:new, :create]
skip_before_action :require_no_authentication
private
def redirect_unless_admin
unless current_user.try(:admin?)
flash[:error] = "Only admins can do that"
redirect_to root_path
end
end
def sign_up(resource_name, resource)
true
end
end
# config/routes.rb
Rails.application.routes.draw do
devise_for :users, :controllers => { :registrations => 'registrations'}
end
Explaination:
Subclass the registration controller and create the route for it.
The before_action ensures that a user is logged in, and redirects them unless they are admin, if they try to sign up.
The already logged in issue is caused by Devise's require_no_authentication method and skipping it resolves the issue.
Next is that the newly created user is automatically signed in. The sign_up helper method which does this is overridden to do prevent automatic signup.
Finally, the Welcome! You have signed up successfully. sign up flash message can be changed via editing the config/locales/devise.en.yml file, if desired.
The problem is conceptual. Devise is only an Authentication library not an Authorization library. You have to implement this separately or use CanCan. Fret not however, it is easy in your case to implement this since you only have one role.
Guard your user create/update/destroy action with a before filter:
class UsersController < ApplicationController
before_filter :authorize_admin, except [:index, :show]
def create
# user create code (can't get here if not admin)
end
end
class ApplicationController < ActionController::Base
def authorize_admin
redirect_to root_path, alert: 'Access Denied' unless current_user.admin?
end
end
With this simple approach you run a before filter on any controller action that can affect a user record by first checking if the user is an admin and kicking them out to the home page if they're not.
I had this issue when working on a Rails 6 application.
I had an admin model which I Devise used to create admins.
Here's how I fixed it:
First I generated a Devise controller for the Admin model, which created the following files:
app/controllers/admins/confirmations_controller.rb
app/controllers/admins/omniauth_callbacks_controller.rb
app/controllers/admins/passwords_controller.rb
app/controllers/admins/registrations_controller.rb
app/controllers/admins/sessions_controller.rb
app/controllers/admins/unlocks_controller.rb
Next, I modified the new and create actions of the app/controllers/admins/registrations_controller.rb this way to restrict signup to admin using Devise:
class Admins::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
def new
if admin_signed_in?
super
else
redirect_to root_path
end
end
# POST /resource
def create
if admin_signed_in?
super
else
redirect_to root_path
end
end
.
.
.
end
Afterwhich, I added the skip_before_action :require_no_authentication, only: [:new, :create] action to the app/controllers/admins/registrations_controller.rb this way to allow an already existing admin to create a new admin while being logged in:
class Admins::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
skip_before_action :require_no_authentication, only: [:new, :create]
.
.
.
end
You may need to also hide the Register and Forgot password buttons in the login form, to only been by logged in admins:
# app/views/admins/sessions/new.html.erb
<% if admin_signed_in? %>
<%= render "admins/shared/links" %>
<% end %>
Optionally, I added first_name and last_name parameters to the allowed parameters in the app/controllers/admins/registrations_controller.rb file, after already adding it to the admin model through a database migration:
class Admins::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
skip_before_action :require_no_authentication, only: [:new, :create]
.
.
.
# If you have extra params to permit, append them to the sanitizer.
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:first_name,
:last_name])
end
# If you have extra params to permit, append them to the sanitizer.
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:first_name,
:last_name])
end
.
.
.
end
Note: You will need to modify your app/views/admin/registrations views accordingly as well to cater for this
Additionally, you may need to create a new controller where you can create the index action for admins, say, app/controllers/admins/admins_controller.rb:
class Admins::AdminsController < ApplicationController
before_action :set_admin, only: [:show, :edit, :update, :destroy]
before_action :authenticate_admin!
# GET /admins
# GET /admins.json
def index
#admins = Admin.all
end
.
.
.
end
Note: If you need more control like managing admins by updating and deleting information of other admins from your dashboard, you may need to add other action like show, edit, update, destroy, and others to this file.
And for the routes:
## config/routes.rb
# List of Admins
get 'admins', to: 'admins/admins#index'
namespace :admins do
resources :admins
end
And then modify the path used after sign up in the app/controllers/admins/registrations_controller.rb to redirect to the admins_path, this will show you a list of all the admins that have been created:
class Admins::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
before_action :configure_account_update_params, only: [:update]
skip_before_action :require_no_authentication, only: [:new, :create]
.
.
.
# The path used after sign up.
def after_sign_up_path_for(resource)
admins_path
end
# The path used after sign up for inactive accounts.
def after_inactive_sign_up_path_for(resource)
admins_path
end
end
Finally, you may need to change the notice that Devise gives when you create a user in the config/locales/devise.en.yml file, from:
Welcome, You have signed up successfully
to, say:
Account was created successfully.
That's all.
I hope this helps.
Playing around with Rails 4, I noticed that it defines a before_action filter set_[model], that is called for the actions show, edit, update and destroy.
It is in fact very useful, but I don't see much sense in not having it to all member actions. After all, if you are using a member action, you want to act on that member, thus you need to recover it from database at some point.
Note that by member actions, I also means the ones configured on routes.rb in the members block.
Is there a straight-forward way to do this, without list all member actions on the before_action filter?
Edit: To clarify, the whole point is use some rails magic to get all member routes and generate the array that would be pass in the :only. So I can do something like
before_action set_model only: all_member_routes
where all_member_routes is a piece of code that returns all member routes for my model.
Or even better,
before_action set_model, only_member_actions: true
The show, edit, update, destroy actions are precisely all member actions. They are the only ones that require to find the model.
If you have your own member action, then you'll have to add it to the list yourself.
before_action :set_item, only: [:edit, ..., :my_member_action]
Or, you can use the :except option to exclude all the collection actions that do not need it:
before_action :set_item, except: [:index, :create]
This way, if you add other member actions, you wont have to change anything.
Personally, I prefer to be explicit and use :only.
I'm pretty sure there is no easier way to do it, you can't detect all member actions automatically.
edit:
I really don't think you should do that but...
You can access the name of your controller with the controller_name method.
Getting the routes related to the controller:
routes = Rails.application.routes.routes.select { |r| r.defaults[:controller] == controller_name }
Then, I think the best way to see if a route is a member route is that the #parts array includes :id. Maybe you can find a more robust way.
So I would do:
routes.select { |r| r.parts.include?(:id) }.map { |r| r.defaults[:action] }.map &:to_sym
That would give you: [:show, :preview, :my_challenges] for
resources :users, only: [:index, :show], controller: 'accounts/users' do
member do
get :preview
get :my_challenges
end
end
class ApplicationController < ActionController::Base
def member_routes
Rails.application.routes.routes
.select { |r| r.defaults[:controller] == controller_name && r.parts.include?(:id) }
.map { |r| r.defaults[:action] }
.map(&:to_sym)
end
end
class UsersController < ApplicationController
before_action set_model, only: member_routes
end
If you ask for a before_action without :only or :except options, it will apply to all member actions:
class ApplicationController < ActionController::Base
before_action :require_login
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
end
In this particular case, it will require login from all actions on all controllers, since controllers will inherit from ApplicationController.
You can skip a before_action if you need it (for example, you need to skip require_login if you want to login into the system or sign up) like this:
class LoginsController < ApplicationController
skip_before_action :require_login, only: [:new, :create]
end
Source: Rails Guides
So, in your particular case:
You could have one usual UserController:
class UserController < ApplicationController
def index
end
...
/* you define here `index`, `create`, `update`, ... */
def destroy
...
end
end
And you could have a separate controller with all your member actions:
class UserCustomController < ApplicationController
before_action :set_model
def profile
...
end
def preview
end
def custom_member_action
end
...
/* all your member actions */
end
This would be actually better than having a single controller with lots of methods.
help my in that question:
i have 2 models ( admin and user ) -> created with devise,
and i have post_controller:
and the question arises:
if i have one model ( user.rb ) -> in my controller i put that:
before_filter :authenticate_user!, :except => [:show, :index]
but i have 2 models and i want to User have access to 'show' and 'index' action of post controller and Admin have access to all actions.
and i do something like that:
before_filter :logged_in
.
.
.
private
def logged_in
if admin_signed_in?
else
authenticate_user!
end
end
but i want change my string:
authenticate_user!
to something like that:
:authenticate_user!, :except => [:show, :index]
but except refers to before_filter
how can I do it ( without 'cancan' gem )
Try using two before filters - one for admin only actions, and another for admin or user actions.
# ensure admin for other actions
before_filter :check_admin_logged_in!, :except => [:show, :index]
# ensure user or admin logged in for these actions (:only option is optional)
before_filter :check_user_logged_in!, :only => [:show, :index]
private
def check_admin_logged_in! # admin must be logged in
authenticate_admin!
end
def check_user_logged_in! # if admin is not logged in, user must be logged in
if !admin_signed_in?
authenticate_user!
end
end