Rails authority apparent inconsistency - ruby-on-rails

I am using the gems authority and rolify to manage user permissions on a set of subjects.
Each subject can be seen by a user only if the user has the :admin role for that subject.
Code in the view:
<% if (current_user.has_role? :admin, #subject) %>
ADMIN
<% end %>
<% if #subject.readable_by?(current_user)%>
#some other code
<% end %>
Code in the authorizer:
class SubjectAuthorizer < ApplicationAuthorizer
# can the user view the subject?
def self.readable_by?(user)
user.has_role? :admin, #subject
end
end
My problem is that the ADMIN part is displayed, but not the rest of the page. However, the two if conditions should have the same truth value. Can anyone spot a mistake?

#subject is not available in the Authorizer. You need to use resource instead.

Related

Rails require user's profile setup after sign_up

How can I require user to create profile after he has signed up with Devise?
I am thinking about using Wicked Wizard gem, but it is possible for user to skip all steps and just access the website.
User must setup their profile first, only then they should have access to website.
I am thinking about this implementation:
SomeController.rb
before_action :authenticate_user!
before_action :check_if_profile_created?
private
def check_if_profile_created?
current_user.profile
end
end
But this solution will do this check on all requests made by user, which I think is not good. Is there any other ways to do this? Or how can it be implemented with Wicked Wizard gem? I haven't found how to make it redirect from all pages to current step in wizard.
I have a solution that I just implemented two days ago. There is no much documentation on the Internet about this matter. There might be many ways to do this. This is just the way I did it. So, without more delay, let's get to it.
The Concept:
The whole idea, at least how I conceive it, is to "force" the recently signed up user, after he/she also confirms its email, to additionally confirm his profile. As simple as that. That's because in my case many of the profile fields are obviously not mandatory, except the first_name, last_name, email and obviously the password. The rest (job_title, company, affiliation, etc, etc, etc), are not mandatory. But the thing is that at the same time, I would like to enforce that every single User has a profile as complete as possible. This is absolutely necessary for purposes that are not necessary to mention here.
Note:
This is based in that you already have a SettingsController, that allows to any logged user to update his own profile and to update his password as well (in a separated action method).
Step # 1: Add the related boolean field
Modify your users table by adding a boolean field.
class AddProfileWasConfirmedToUsersTable < ActiveRecord::Migration[6.1]
def change
add_column :users, :profile_was_confirmed, :boolean, null: false, default: false
end
end
The boolean field profile_was_confirmed will ensure that the User's profile has been confirmed. It's false by default, right from creation, and it will be updated once and only once to true, when the user confirms his profile. The system won't enforce a 100% complete profile after that, because in order to do such thing it would have to be mandatory on the User model, and that would mess up with the initial sign up process (using the usual devise).
Step # 2: Add the necessary routes.
This one is for the view where we will include a form (inn our case reuse the settings profile form):
get :confirm_profile, to: 'settings#confirm_profile'
And this one is for the updating of the incoming fields from the previously mentioned form:
patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
So at the end you would have something like this (just an example):
get :confirm_profile, to: 'settings#confirm_profile'
resource :settings, only: [:edit, :update] do
patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
patch :change_password, to: "settings#change_password"
resource :site_terms, only: [:edit, :update]
end
Note:
Remember to place the additional get route before the resources declaration. Otherwise the framework will confuse your route with the show action method.
Step # 3: Modify your ApplicationsController
before_action :check_profile_was_confirmed
.
.
.
def check_profile_was_confirmed
return unless current_user.present?
unless current_user.profile_was_confirmed? || devise_controller?
redirect_to confirm_profile_path
end
end
Step # 3: Modify your SettingsController
skip_before_action :check_profile_was_confirmed
def confirm_profile
if current_user.profile_was_confirmed?
flash["notice"] = "Your profile has been already confirmed"
redirect_to dashboard_path
end
end
def update_confirmed_profile
if #user.update(update_params) && confirm_params_ready?
#user.update({ profile_was_confirmed: true })
flash["notice"] = "Profile confirmed"
redirect_to dashboard_path
else
flash["alert"] = "All the fields must be filled"
redirect_to confirm_profile_path
end
end
private
def confirm_params_ready?
params_base = params.require(:user).permit(:first_name, :last_name, :email, :company, :job_title)
params_base.values.all?(&:present?)
end
Step 4: Create your view with the form or reuse the same one you are using for updating the profile.
File:
app/views/settings/confirm_profile.html.erb
<% content_for(:title) { " | Profile Confirmation" } %>
<% content_for(:view_header) do %>
<%= render 'shared/view_header', view_title: 'Profile Confirmation' %>
<% end %>
<%#= render partial: 'nav' %>
<div class="w-prose mx-auto space-y-8">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Fill all the fields</h4>
</div>
<div class="card-body">
<%= render partial: "settings/profile_form", locals: { form_url: update_confirmed_profile_settings_path } %>
</div>
</div>
</div>
Step 5: Update your User model's factory
Step 6: Update all your integration tests.
there is no way you can do it without a validation on every request, because you always have to be sure they fill the profile, no matter on what page you enter.
maybe you can add that validation to the application controller with a before action so you don't have to do it in every controller. and just validate if user is logged in and user.profile...
What do you mean ugly? If it's a matter of "the code will be duplicated everywhere", then you can create a controller that inherits from ActionController, and add before_action to that controller specifying that the profile needs to be filled up. Or you can create a concern which you can then plop in
class SomethingController < ApplicationController
before_action :ensure_profile_filled
def ensure_profile_filled
redirect_to 'wherever'
end
end
class ChildController < SomethingController
end
There are also route constraints but I think you want to do this on the controller layer.

How to allow Admin user to access customer user objects rails

I have a report object that belongs_to a customer(user_type). I have it setup so that the customer is able to grant or remove permission from an admin to see the customers report. I need to know what steps or direction do I need to take to get the reports viewable to the admin user. The admin user should only have the ability to see reports (show) and see an index of reports that they have access to.
I am thinking maybe I should create a new controller but I am not sure if that is correct or how to do another controller tied to an object that already has a controller.
I highly suggest looking into the CanCanCan gem (the continuation of the CanCan gem which is no longer supported). In this way your code would look something like this in the end (you didn't post any code, so I have to make assumptions about your variables and such):
<% if current_user.admin? %>
<% if can? :view_report, #customer %>
<!-- Render the report to this admin -->
<!-- ............................. -->
<% end %>
<% end %>
And then you would have something like this in your controller to give an admin permission to view the user's report:
def handle_report
can [:show], Report, :user_id => user.id
if current_user.admin?
can :manage, :current_report
end
end
Again, apologies for guessing the variables.

Rails: How to only allow User to apply to job only once?

I am creating a job board, and I don't want to allow the users the option to apply for the same job twice. How can I limit this?
app/views/jobs/job.html.erb
<% if applied_to_this_job? %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
app/helpers/jobs_helper.rb
def applied_to_this_job?
JobApplication.exists? user_id: current_user.id
end
Obviously this doesn't work because it checks if this user has applied to any job. How Can I check to see if the current user has applied to the job being viewed.
Also, how can I limit this at the controller level so that the user can't go to job_application/new and get to the form.
You would use a before_filter in the controller action.
class JobsController < ActionController::Base
before_filter :has_applied?, only: [new, create]
....
private
def has_applied?
if JobApplication.where(user_id: :current_user.id, job_id: params[:job_id]).any?
redirect_to :index, alert: "You have already applied"
end
end
end
This would allow the user to visit /jobs/new and post the application to /jobs/create unless they have applied. If they have applied, they will be redirected to the index in the sample code.
Also as another answer has noted, it would be wise to pass in the job id as well. Updated sample code above to reflect.
You need to check and see if the JobApplication object is for this #job try:
JobApplication.where( user_id: current_user.id, job_id: #job.id ).exists?
Although what you've accepted will work, I think it's somewhat of a surface-level fix.
You'll be much better using validators to determine if the user can actually create another job application. This will protect against any problems with the business logic in your "front-end" views
Here's how I'd handle it:
--
Uniq
#app/models/user.rb
class User < ActiveRecord::Base
has_one :job_application
end
#app/models/job_application.rb
class JobApplication < ActiveRecord::Base
belongs_to :user
validates :user_id, uniquness: true
end
You may also wish to give your database a uniq index for your user_id column:
> $ rails g migration AddUniqueIndex
#config/db/add_unique_index.rb
class AddUniqueIndex < ActiveRecord::Migration
def change
add_index :job_applications, [:job_id, :user_id], unique: true
end
end
This will give you a highly efficient DB-level uniqueness index - meaning that if you try and add any more applications than is permitted, it will either fail silently, or come back with an error.
Controller
The structure of the controller would allow you to be less stringent about the accessibility of the job_application functionality:
#app/views/jobs/job.html.erb
<% if current_user.has_applied?(params[:job_id]) %>
<div class="alert" role="alert">You have already applied to this job!</div>
<% else %>
<%= link_to 'Apply', new_job_application_path(#job) %>
<% end %>
#app/models/user.rb
class User < ActiveRecord::Base
has_many :job_applications
def has_applied?(job_id)
job_applications.find job_id
end
end

Rails 4: form_for with nested resource and without

In my app, when a regular user logs in, they are dropped in on a dashboard that displays Service Requests for the company they belong_to.
When an admin logs in, they are dropped onto a dashboard that displays all of the company logos that can login and file Service Requests.
The views between a regular user and an admin user are virtually the exact same, outside of one or two entities on the form (which are controlled via cancan). So I am trying to be able to use the same form for if an admin creates an SR or a regular user creates an SR.
Routes.rb:
resources :service_requests do
resources :notes
end
namespace :admin do
get '', to: 'dashboard#index', as: '/'
resources :companies do
resources :service_requests, only: [:index, :new]
end
end
If an admin logs in and clicks on a company logo and clicks to create a new SR, the route is /admin/companies/1/service_requests/new. If a regular user logs in it is, /service_requests/new. I am just slightly confused on how to reuse the same form for both the admin and non-admin side. Because I am setting the company_id on the SR in my create resource in the ServiceRequestsController
I do something like the following to use the same form on an admin and a not admin using cancan
#models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user.is_admin? #replace this by something that returns true if logged-in user is admin
can :generate_this_form_fields, User
# ^ normally can -> points to a method in the controller, but you can
# create one even if you're not validating any method, just so you could use it anywhere
end
end
end
While on the view file where your form is, something like
#views/form.html.erb
<%= form_tag ... %>
<% if can? :generate_this_form_fields, User %>
This is where the fields are displayed if admin account
<% else %>
This is where the fields are displayed if not admin
<% end %>
<% end %>
Actually in the view, you could just use
<% if current_user.is_admin? %>
And not
<% if can? :generate_this_form_fields, User %>
But I've made the example in such a way that might fit to what you're trying to solve.
Hope this helps. Cheers!

Double controllers when using namespace in rails?

I'm working on an application with a namespacing (admin section). Is there a DRY solution for not creating 2 controllers? Because I will need to create an public user controller and a admin user controller to manage the users.
How about inheriting the user controller? I use it myself (for images) and it suits me nicely:
# file: apps/controllers/images_controller.rb
class ImagesController < ApplicationController
# image code (to show the image for example)
end
# file: apps/controllers/admin/images_controller.rb
class Admin::ImagesCOntroller < ImagesController
# additional admin code (to delete the image for example)
end
You might consider rendering the page with optional "edit" buttons. For example, something like this:
Name: <%= #user.name %>
<% if #user.admin? %>
<% form_for #user do |f| %>
Editing stuff
<% end %>
<% end %>
That way, a user only sees it as a page, but an admin sees additional controls that allows them to edit the field. P.S. Make sure in your controller that you are checking to make sure it's an admin that is calling the update call.

Resources