Implementing a policy class using Pundit - Rails - ruby-on-rails

I writing a class called AdminPolicy This class is basically trying to say whether or not a user is an admin... Right now I pass in the current_user and the current_account. I basically have a join to table between user and account and that has the admin attribute on it. Everything seems to be working fine except for this error that I'm getting undefined local variable or method 'admin_policy'. Here is my code, I can't seem to figure out what is wrong?
admin policy class
class AdminPolicy < ApplicationPolicy
attr_reader :user, :account
def initialize(user, account)
#user = user
#account = account
end
def allow_access?(user, account)
membership = account.membership_for?(user)
if membership.admin
true
else
false
end
end
end
pages controller
def admin_policy
#admin_policy ||= AdminPolicy.new(current_user, current_account)
end
dashboard.html.erb
<% if admin_policy.allow_access?(current_user, current_account) %>
<div class="admin-dashboard">
<%= render partial: "admin_dashboard" %>
</div>
<% else %>
<div class="worker-dashboard">
<%= render partial: "worker_dashboard" %>
</div>
<% end %>
Error
So its saying that admin_policy is undefined.. Any idea why this would be?

First, allow_access? doesn't need to receive parameters, so your policy should be
class AdminPolicy < ApplicationPolicy
attr_reader :user, :account
def initialize(user, account)
#user = user
#account = account
end
def allow_access?
membership = account.membership_for?(user)
if membership.admin
true
else
false
end
end
end
In your views, you can call it in this way (this is not the only way to do it)
<% if policy(Admin).allow_access? %>
<div class="admin-dashboard">
<%= render partial: "admin_dashboard" %>
</div>
<% else %>
<div class="worker-dashboard">
<%= render partial: "worker_dashboard" %>
</div>
<% end %>
Be sure to read carefully the documentation.
Heads up!
If you are inheriting from ApplicationPolicy you don't need to include attr_reader and ìnitialize method because both are defined by default in the ÀpplicationPolicy

Try this
<% if #admin_policy.allow_access?(current_user, current_account) %>
Hope this will helping you.

Related

How can show the data related to logged in user in rails

I am trying display the task related to logged in user but on my html page nothing show except the tag data
task_controller.rb
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(#current_user.id)
render template: 'task/allTask'
end
end
end
routes.rb
get 'all_task' => 'task#all_task'
task.erb
<p>All Task</p>
<% if user_signed_in? %>
<%#all_task.daily_task %>
<%#all_task.date %>
<%#all_task.created_at %>
<%end %>
Start by setting up an assocation between users and tasks:
class User < ApplicationRecord
# ...
has_many :tasks
end
Then setup the route and controller:
get '/user/tasks', to: 'users/tasks#index', as: :user_tasks
# app/controllers/users/tasks_controller.rb
module Users
class TasksController < ApplicationRecord
before_action :authenticate_user!
# display all the tasks belonging to the currently signed in user
# GET /user/tasks
def index
#tasks = current_user.tasks
end
private
# You don't need this if your using Devise
def authenticate_user!
unless current_user
redirect_to '/path/to/your/login',
notice: 'Please sign in before continuing'
end
end
end
end
Note that when you have a route like this that displays resources that belong to the current user you should use a callback to bail early and redirect the user to sign in instead of using if current_user.present? and giving a response which is meaningless to the user. This code should be DRY:ed into your ApplicationController (even better yet is to not reinvent the auth wheel).
You can link to the users tasks with:
<% if current_user.present? %>
<%= link_to 'My tasks', user_tasks_path %>
<% end %>
In your view you need to iterate across the returned tasks:
# app/views/users/tasks/index.html.erb
<p>All Tasks</p>
<% if #tasks.any? %>
<% #tasks.each do |task| %>
<%= task.daily_task %>
<%= task.date %>
<%= task.created_at %>
<% end %>
<% else %>
<p>You don't have any tasks.</p>
<% end %>
You can cut duplication here by using partials.
Can you make sure if the instance variable #current_user is defined? If not, try the following:
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(current_user.id)
render template: 'task/allTask'
end
end
end
instead of
class TaskController < ApplicationController
def all_task
if current_user.present?
#all_task = Task.find_by_user_id(#current_user.id)
render template: 'task/allTask'
end
end
end

How to write a custom function in a model in ruby on rails?

I have a User model class I have created with Devise.
I have a role field (admin=0, user=1) in my model.
Screenshot of my database:
HTML View
<% if current_user.active_admin? %>
<%= render 'layouts/admin' %>
<% else %>
<%= render 'layouts/user' %>
<% end %>
Model
def active_admin?
#your logic here
end
I want to login. If I am an admin check role is 0 render to layouts/admin else I am a user check role is 1 render to layouts/user.
How do I write code in the model to do this?
In your user.rb file:
class User < ActiveRecord::Base
def active_admin?
role == 0
end
end
In your view:
<% if current_user.active_admin? %>
<%= render 'layouts/admin' %>
<% else %>
<%= render 'layouts/user' %>
<% end %>
As Mark says, you can just check role for 0 or 1.
Any column in your database will map directly to a method on the model.
A couple of points:
If you're using Rails 5 you'll need to inherit from ApplicationRecord rather than ActiveRecord::Base.
In newer versions of Ruby you can use the #zero? method:
class User < ApplicationRecord
def active_admin?
role.zero?
end
end
No need of adding a method for checking the roles, you can directly achieve this by below change. It will return true for anything other than 0.
<% if current_user.role? %>
<%= render 'layouts/user' %>
<% else %>
<%= render 'layouts/admin' %>
<% end %>
One way can be add an method on application controller as
def after_sign_in_path_for(resource_or_scope)
if current_user.role==0
#your admin path
else
root_path
end
end
def authenticate_admin
unless (user_signed_in? and current_user.role !=0 )
redirect_to '/users/sign_in'
end
end
add to the required controller
before_filter :authenticate_admin
layout 'admin'

Error when creating new record of Model in Rails 5

I have 2 models in my rails application. User and Goal.
I have set them like this:
User Model
class User < ApplicationRecord
has_one :goal, dependent: :destroy
end
Goal Model
class Goal < ApplicationRecord
belongs_to :user, optional: true
end
Whenever I try to make a new record of the Goal model I get this error:
undefined method `new' for nil:NilClass
Here's my controller and view for the Goal model
Goals Controller
class GoalsController < ApplicationController
def index
end
def new
#goal = Goal.new
end
def create
#goal = current_user.goal.new(goal_params)
if #goal.save
redirect_to #goal
else
render 'new'
end
end
private
def goal_params
params.require(:goal).permit(:user_id, :goal_type)
end
end
Goals View (new action)
<%= form_for(#goal) do |f| %>
<div class="field">
<%= f.label :goal_type, "Would you like to..." %>
<%= f.select :goal_type, ["Loose weight", "Gain weight", "Keep current weight"] %>
</div>
<div class="field submit">
<%= f.submit "Submit", class: "button button-highlight button-block" %>
</div>
<% end %>
In my goals table I have a column called goal_type and user_id.
I need to make it so that when creating a new record the user_id field gets automatically filled with the current_user id (using devise of course).
Thanks in advance!
I just changed the controller from:
#goal = current_user.goal.new(goal_params)
to:
#goal = current_user.build_goal(goal_params)

Rails Form Object with Virtus: has_many association

I am having a tough time figuring out how to make a form_object that creates multiple associated objects for a has_many association with the virtus gem.
Below is a contrived example where a form object might be overkill, but it does show the issue I am having:
Lets say there is a user_form object that creates a user record, and then a couple associated user_email records. Here are the models:
# models/user.rb
class User < ApplicationRecord
has_many :user_emails
end
# models/user_email.rb
class UserEmail < ApplicationRecord
belongs_to :user
end
I proceed to create a a form object to represent the user form:
# app/forms/user_form.rb
class UserForm
include ActiveModel::Model
include Virtus.model
attribute :name, String
attribute :emails, Array[EmailForm]
validates :name, presence: true
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
puts "The Form is VALID!"
puts "I would proceed to create all the necessary objects by hand"
# user = User.create(name: name)
# emails.each do |email_form|
# UserEmail.create(user: user, email: email_form.email_text)
# end
end
end
One will notice in the UserForm class that I have the attribute :emails, Array[EmailForm]. This is an attempt to validate and capture the data that will be persisted for the associated user_email records. Here is the Embedded Value form for a user_email record:
# app/forms/email_form.rb
# Note: this form is an "Embedded Value" Form Utilized in user_form.rb
class EmailForm
include ActiveModel::Model
include Virtus.model
attribute :email_text, String
validates :email_text, presence: true
end
Now I will go ahead and show the users_controller which sets up the user_form.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user_form = UserForm.new
#user_form.emails = [EmailForm.new, EmailForm.new, EmailForm.new]
end
def create
#user_form = UserForm.new(user_form_params)
if #user_form.save
redirect_to #user, notice: 'User was successfully created.'
else
render :new
end
end
private
def user_form_params
params.require(:user_form).permit(:name, {emails: [:email_text]})
end
end
The new.html.erb:
<h1>New User</h1>
<%= render 'form', user_form: #user_form %>
And the _form.html.erb:
<%= form_for(user_form, url: users_path) do |f| %>
<% if user_form.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user_form.errors.count, "error") %> prohibited this User from being saved:</h2>
<ul>
<% user_form.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<% unique_index = 0 %>
<% f.object.emails.each do |email| %>
<%= label_tag "user_form[emails][#{unique_index}][email_text]","Email" %>
<%= text_field_tag "user_form[emails][#{unique_index}][email_text]" %>
<% unique_index += 1 %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Note: If there is an easier, more conventional way to display the inputs for the user_emails in this form object: let me know. I could not get fields_for to work. As shown above: I had to write out the name attributes by hand.
The good news is that the form does render:
The html of the form looks ok to me:
When the above input is submitted: Here is the params hash:
Parameters: {"utf8"=>"✓", "authenticity_token"=>”abc123==", "user_form"=>{"name"=>"neil", "emails"=>{"0"=>{"email_text"=>"foofoo"}, "1"=>{"email_text"=>"bazzbazz"}, "2"=>{"email_text"=>""}}}, "commit"=>"Create User form"}
The params hash looks ok to me.
In the logs I get two deprecation warnings which makes me think that virtus might be outdated and thus no longer a working solution for form objects in rails:
DEPRECATION WARNING: Method to_hash is deprecated and will be removed in Rails 5.1, as ActionController::Parameters no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (called from new at (pry):1)
DEPRECATION WARNING: Method to_a is deprecated and will be removed in Rails 5.1, as ActionController::Parameters no longer inherits from hash. Using this deprecated behavior exposes potential security problems. If you continue to use this method you may be creating a security vulnerability in your app that can be exploited. Instead, consider using one of these documented methods which are not deprecated: http://api.rubyonrails.org/v5.0.2/classes/ActionController/Parameters.html (called from new at (pry):1)
NoMethodError: Expected ["0", "foofoo"} permitted: true>] to respond to #to_hash
from /Users/neillocal/.rvm/gems/ruby-2.3.1/gems/virtus-1.0.5/lib/virtus/attribute_set.rb:196:in `coerce'
And then the whole thing errors out with the following message:
Expected ["0", <ActionController::Parameters {"email_text"=>"foofoo"} permitted: true>] to respond to #to_hash
I feel like I am either close and am missing something small in order for it to work, or I am realizing that virtus is outdated and no longer usable (via the deprecation warnings).
Resources I looked at:
this article.
this video
I did attempt to get the same form to work but with the reform-rails gem. I ran into an issue there too. That question is posted here.
Thanks in advance!
I would just set the emails_attributes from user_form_params in the user_form.rb as a setter method. That way you don't have to customize the form fields.
Complete Answer:
Models:
#app/modeles/user.rb
class User < ApplicationRecord
has_many :user_emails
end
#app/modeles/user_email.rb
class UserEmail < ApplicationRecord
# contains the attribute: #email
belongs_to :user
end
Form Objects:
# app/forms/user_form.rb
class UserForm
include ActiveModel::Model
include Virtus.model
attribute :name, String
validates :name, presence: true
validate :all_emails_valid
attr_accessor :emails
def emails_attributes=(attributes)
#emails ||= []
attributes.each do |_int, email_params|
email = EmailForm.new(email_params)
#emails.push(email)
end
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
user = User.new(name: name)
new_emails = emails.map do |email|
UserEmail.new(email: email.email_text)
end
user.user_emails = new_emails
user.save!
end
def all_emails_valid
emails.each do |email_form|
errors.add(:base, "Email Must Be Present") unless email_form.valid?
end
throw(:abort) if errors.any?
end
end
# app/forms/email_form.rb
# "Embedded Value" Form Object. Utilized within the user_form object.
class EmailForm
include ActiveModel::Model
include Virtus.model
attribute :email_text, String
validates :email_text, presence: true
end
Controller:
# app/users_controller.rb
class UsersController < ApplicationController
def index
#users = User.all
end
def new
#user_form = UserForm.new
#user_form.emails = [EmailForm.new, EmailForm.new, EmailForm.new]
end
def create
#user_form = UserForm.new(user_form_params)
if #user_form.save
redirect_to users_path, notice: 'User was successfully created.'
else
render :new
end
end
private
def user_form_params
params.require(:user_form).permit(:name, {emails_attributes: [:email_text]})
end
end
Views:
#app/views/users/new.html.erb
<h1>New User</h1>
<%= render 'form', user_form: #user_form %>
#app/views/users/_form.html.erb
<%= form_for(user_form, url: users_path) do |f| %>
<% if user_form.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(user_form.errors.count, "error") %> prohibited this User from being saved:</h2>
<ul>
<% user_form.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= f.fields_for :emails do |email_form| %>
<div class="field">
<%= email_form.label :email_text %>
<%= email_form.text_field :email_text %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
You have an issue because you haven't whitelisted any attributes under :emails. This is confusing, but this wonderful tip from Pat Shaughnessy should help set you straight.
This is what you're looking for, though:
params.require(:user_form).permit(:name, { emails: [:email_text, :id] })
Note the id attribute: it's important for updating the records. You'll need to be sure you account for that case in your form objects.
If all this form object malarkey with Virtus gets to be too much, consider Reform. It has a similar approach, but its raison d'etre is decoupling forms from models.
You also have an issue with your form… I'm not sure what you were hoping to achieve with the syntax you're using, but if you look at your HTML you'll see that your input names aren't going to pan out. Try something more traditional instead:
<%= f.fields_for :emails do |ff| %>
<%= ff.text_field :email_text %>
<% end %>
With this you'll get names like user_form[emails][][email_text], which Rails will conveniently slice and dice into something like this:
user_form: {
emails: [
{ email_text: '...', id: '...' },
{ ... }
]
}
Which you can whitelist with the above solution.
The problem is that the format of the JSON being passed to UserForm.new() is not what is expected.
The JSON that you are passing to it, in the user_form_params variable, currently has this format:
{
"name":"testform",
"emails":{
"0":{
"email_text":"email1#test.com"
},
"1":{
"email_text":"email2#test.com"
},
"2":{
"email_text":"email3#test.com"
}
}
}
UserForm.new() is actually expecting the data in this format:
{
"name":"testform",
"emails":[
{"email_text":"email1#test.com"},
{"email_text":"email2#test.com"},
{"email_text":"email3#test.com"}
}
}
You need to change the format of the JSON, before passing it to UserForm.new(). If you change your create method to the following, you won't see that error anymore.
def create
emails = []
user_form_params[:emails].each_with_index do |email, i|
emails.push({"email_text": email[1][:email_text]})
end
#user_form = UserForm.new(name: user_form_params[:name], emails: emails)
if #user_form.save
redirect_to #user, notice: 'User was successfully created.'
else
render :new
end
end

Nested sign up form does not display the error messages for the nested object

Organization and User have a many-to-many relationship through Relationship. There's a joined signup form. The sign up form works in that valid information is saved while if there's invalid information it rolls back everything.
The problem is that the form does not display the error messages for the nested User object. Errors for Organization are displayed, the form correctly re-renders if there are errors for User, but the errors for User are not displayed.
Why are the errors when submitting invalid information for users not displayed? Any help is appreciated.
The signup form/view:
<%= form_for #organization, url: next_url do |f| %>
<%= render partial: 'shared/error_messages', locals: { object: f.object, nested_models: f.object.users } %>
... fields for organization...
<%= f.fields_for :users do |p| %>
...fields for users...
<% end %>
<%= f.submit "Register" %>
<% end %>
The shared error messages partial:
<% if object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% if defined?(nested_models) && nested_models.any? %>
<div id="error_explanation">
<ul>
<% nested_models.each do |nested_model| %>
<% if nested_model.errors.any? %>
<ul>
<% nested_model.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<% end %>
</ul>
</div>
<% end %>
The controller method:
def new
#organization = Organization.new
#user = #organization.users.build
end
def create
#organization = Organization.new(new_params.except(:users_attributes))
#organization.transaction do
if #organization.valid?
#organization.save
begin
#user = #organization.users.create!(users_attributes)
#relationship = #organization.relationships.where(user: #user).first
#relationship.update_attributes!(member: true, moderator: true)
rescue
raise ActiveRecord::Rollback
end
end
end
if #organization.persisted?
if #organization.relationships.where('member = ? ', true).any?
#organization.users.where('member = ? ', true).each do |single_user|
single_user.send_activation_email
end
end
flash[:success] = "A confirmation email is sent."
redirect_to root_url
else
#user = #organization.users.build(users_attributes) if #organization.users.blank?
render :new
end
end
The Organization model:
has_many :relationships, dependent: :destroy
has_many :users, through: :relationships, inverse_of: :organizations
accepts_nested_attributes_for :users, :reject_if => :all_blank, :allow_destroy => true
validates_associated :users
The Relationship model:
belongs_to :organization
belongs_to :user
The User model:
has_many :relationships, dependent: :destroy
has_many :organizations, through: :relationships, inverse_of: :users
Update: If I add an additional line to def create as below, it seems to work, i.e., then it does display the error messages. However, then it for some reason doesn't save when valid information is submitted. Any ideas how to deal with that?
def create
#organization = Organization.new(new_params.except(:users_attributes))
#user = #organization.users.new(users_attributes)
#organization.transaction do
...
Maybe try this:
<%= render partial: 'shared/error_messages',
locals: { object: f.object, nested_models: [ #user ] } %>
I guess the call to #organization.users.blank? doesn't work in the way you expected it to do, as the user is not correctly created, because #create! threw an exeption. Rails probably does a check on the database, to see if there are any users now, and thinks there is still nothing in there. So your #organization.users.build(users_attributes) gets called, but this doesn't trigger validation.
In general I would also recommend you the use of a form object (like in the other answer), when creating complex forms, as this clarifies things like that and makes the view more clean.
This is classic use case for form objects. It is convenient from many perpectives (testing, maintainance ...).
For example:
class Forms::Registration
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
def persisted?
false
end
def initialize(attributes = {})
%w(name other_attributes).each do |attribute|
send("#{attribute}=", attributes[attribute])
end
end
validates :name, presence: true
validate do
[user, organization].each do |object|
unless object.valid?
object.errors.each do |key, values|
errors[key] = values
end
end
end
end
def user
#user ||= User.new
end
def organization
#organization ||= Organization.new
end
def save
return false unless valid?
if create_objects
# after saving business logic
else
false
end
end
private
def create_objects
ActiveRecord::Base.transaction do
user.save!
organization.save!
end
rescue
false
end
end
the controller:
class RegistrationsController < ApplicationController
def new
#registration = Forms::Registration.new
end
def create
#registration = Forms::Registration.new params[:registration]
if #registration.save
redirect_to root_path
else
render action: :new
end
end
end
and the view in HAML:
= form_for #registration, url: registrations_path, as: :registration do |f|
= f.error_messages
= f.label :name
= f.text_field :name
= f.submit
It is worth to read more about form objects.
Nested attributes bit me SOOO hard every time I decided it's a good time to use them, and I see you know a bit of what I'm talking about.
Here's a suggestion of a different approach, use a form object instead of nested attributes: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ see under section 3. Extract Form Objects
You can extract your existing validations on the User model into a module and import that, to expand on the solution from the blog:
https://gist.github.com/bbozo/50f8638787d6eb63aff4
With this approach you can make your controller code super-simple and make simple and fast unit tests of the not-so-simple logic that you implemented inside and save yourself from writing integration tests to test out all different possible scenarios.
Also, you might find out that a bunch of the validations in the user model are actually only within the concern of the signup form and that those validations will come and bite in later complex forms, especially if you're importing data from a legacy application where validations weren't so strict, or when you one day add additional validators and make half of your user records invalid for update.
I had a similar problem. everything seemed to work fine, but I was not getting any errors The solution i found is to build the comment in article#show instead of the view:
#article = Article.find(params[:id])
#comment = #article.comments.build(params[:comment])
and in your articles#show don't use #article.comments.build but #comment:
<%= form_for([#article, #comment]) do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<p><%= f.submit %></p>
<% end %>
make sure you build the comment in your comment#create as well (you really have no choice though :P)
I think you need to pass f.object instead of #comment.
In case someone might be looking for a solution to render form errors in a form, try:
f.object.errors["account.address"].present?`
The address is the nested attribute here.

Resources