I have the following model :
class Member
belongs_to :enterprise
class Enterprise
has_many :members
has_many :enterprise_projects
has_many :projects, through: :enterprise_projects
class Project
has_many :enterprise_projects
has_many :projects, through: :enterprise_projects
class EnterpriseProject
belongs_to :project
belongs_to :enterprise
I also have the following permissions defined in ability.rb
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Enterprise, members: { id: user.id }
can :manage, Project, enterprises: { members: { id: user.id } }
end
Combined with the following ProjectsController :
class ProjectsController < ApiController
load_resource :enterprise, through: :current_member, singleton: true
load_resource except: :create
authorize_resource
def index
render json: #projects
end
def create
project = Project.create(create_params)
#enterprise.enterprise_projects.create!(project: project)
project.save!
render json: project
end
Which allows me to restrict restrict the management of projects from a member to only projects associated to its enterprise. (and a project created by a member will be attached to its enterprise). However, if a member fetches a project, the enterprises attribute of the serialized object comprises all the enterprises attached to the project, but I would like it to be only the list of enterprises the member has access to (in this case it would be only one since member belongs to one company).
How can this be achieved ?
Related
I have a users table in my db. A user can be either of type 'admin' or 'manager'.
Given the models and schema below, I would like that for each instance of 'manager' user, an 'admin' user could select one, some or all the locations of the tenant that the manager belongs to in order to select which locations the manager can have control over.
My models
class User < ActiveRecord::Base
belongs_to :tenant
class Tenant < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :locations, dependent: :destroy
class Location < ActiveRecord::Base
belongs_to :tenant, inverse_of: :locations
I've tried two paths
First, trying to establish a scoped has_many association between the User and the Location models. However, I can't wrap my head around structuring this scope so that an 'admin' user could select which locations the 'manager' users can control.
Second, setting up a controlled_locations attribute in the users table. Then I set up some code so that an 'admin' user can select which locations a 'manager' can control, populating its 'controlled_locations' attribute. However, what gets saved in the database (inside the controlled_locations array) is strings instead of instances of locations.
Here's the code that I tried for the second path:
The migration
def change
add_column :users, :controlled_locations, :string, array: true, default: []
end
In the view
= f.input :controlled_locations, label: 'Select', collection: #tenant_locations, include_blank: "Anything", wrapper_html: { class: 'form-group' }, as: :check_boxes, include_hidden: false, input_html: {multiple: true}
In the users controller (inside the update method)
if params["user"]["controlled_locations"]
params["user"]["controlled_locations"].each do |l|
resource.controlled_locations << Location.find(l.to_i)
end
resource.save!
end
What I expect
First of all, I'm not quite sure the second path that I tried is a good approach (storing arrays in the db). So my best choice would be to set up a scoped association if it's possible.
In case the second path is feasible, what I would like to get is something like this. Let's say that logging in an Admin, I selected that the user with ID 1 (a manager) can control one location (Boston Stadium):
user = User.find(1)
user.controlled_locations = [#<Location id: 55, name: "Boston Stadium", created_at: "2018-10-03 12:45:58", updated_at: "2018-10-03 12:45:58", tenant_id: 5>]
Instead, what I get after trying is this:
user = User.find(1)
user.controlled_locations = ["#<Location:0x007fd2be0717a8>"]
Instead of instances of locations, what gets saved in the array is just plain strings.
First, your code is missing the locations association in the Tenant class.
class Tenant < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :locations
Let's say the variable manager has a User record. Then the locations it can control are:
manager.tenant.locations
If you want, you can shorten this with a delegate statement.
class User < ActiveRecord::Base
belongs_to :tenant
delegate :locations, to: :tenant
then you can call this with
manager.locations
A common pattern used for authorization is roles:
class User < ApplicationRecord
has_many :user_roles
has_many :roles, through: :user_roles
def add_role(name, location)
self.roles << Role.find_or_create_by(name: name, location: location)
end
def has_role?(name, location)
self.roles.exists?(name: name, location: location)
end
end
# rails g model role name:string
# make sure you add a unique index on name and location
class Role < ApplicationRecord
belongs_to :location
has_many :user_roles
has_many :users, through: :user_roles
validates_uniqueness_of :name, scope: :location_id
end
# rails g model user_role user:references role:references
# make sure you add a unique compound index on role_id and user_id
class UserRole < ApplicationRecord
belongs_to :role
belongs_to :user
validates_uniqueness_of :user_id, scope: :role_id
end
class Location < ApplicationRecord
has_many :roles
has_many :users, through: :roles
end
By making the system a bit more generic than say a controlled_locations association you can re-use it for different cases.
Let's say that logging in an Admin, I selected that the user with ID 1
(a manager) can control one location (Boston Stadium)
User.find(1)
.add_role(:manager, Location.find_by(name: "Boston Stadium"))
In actual MVC terms you can do this by setting up roles as a nested resource that can be CRUD'ed just like any other resource. Editing multiple roles in a single form can be done with accepts_nested_attributes or AJAX.
If you want to scope a query by the presence of a role then join the roles and user roles table:
Location.joins(roles: :user_roles)
.where(roles: { name: :manager })
.where(user_roles: { user_id: 1 })
To authenticate a single resource you would do:
class ApplicationController < ActionController::Base
protected
def deny_access
redirect_to "your/sign_in/path", error: 'You are not authorized.'
end
end
class LocationsController < ApplicationController
# ...
def update
#location = Location.find(params[:location_id])
deny_access and return unless current_user.has_role?(:manger, #location)
# ...
end
end
Instead of rolling your own authorization system though I would consider using rolify and pundit.
I have the following model structure:
class Job < ApplicationRecord
belongs_to :location
has_and_belongs_to_many :users
end
class Location < ApplicationRecord
has_and_belongs_to_many :users
has_many :jobs
end
class User < ApplicationRecord
has_and_belongs_to_many :locations
end
This allows me to create jobs for a given location, and then associate/assign users to that job. My user table has an active attribute and I am getting an error when I try to query users that are either already assigned to the job or have a status of active. Here is my query:
#users = #job.location.users.where(users: { id: #job.user_ids }).or(#job.location.users.where(active: true))
The error:
ArgumentError (Relation passed to #or must be structurally compatible. Incompatible values: [:references]):
Thanks in advance.
I think this should work:
#users = #job.location.users.where(id: #job.user_ids).or(#job.location.users.where(active: true))
So, I'm using rails. And here are three classes
class User < ActiveRecord::Base
has_many :ownerships, as: :ownable
has_many :products, through: :ownerships
end
class Manufacturer < ActiveRecord::Base
has_many :ownerships, as: :ownable
has_many :products, through: :ownerships
end
class Product < ActiveRecord::Base
has_many :ownerships
end
# ...
# ownable_id :uuid
# ownable_type :enum
# type :string
# status :enum
class Ownership < ActiveRecord::Base
belongs_to :product
belongs_to :ownable, polymorphic: true
end
So, the situation is, User and Manufacturer can have Product through ownership. And both has ownership as polymorphic association.
And in my controller, I'd like to add a new product right off a user with { type: "PrimaryOwner", status: "Approved" } for the ownership that's going to get created.
The code I'd like to run is simply this...
# product_params is
# {
# name: "My new product ASD-Z23",
# description: "It's a product at the storage number #123QWERTY",
# ownership: {
# type: "PrimaryOwner",
# status: "Approved"
# }
# }
current_user.products.new(product_params)
And what I expect is that code will create a product with the ownership. (Since in the User model, I already told it that it can have product "through" a ownership.)
And of course, this code wasn't working.
Is there any good rails way to do that?
Thanks!
It's not clear if you are trying to create a new ownership for the product, or the ownership already exists and you're trying to use that association. If the latter, then you need to use nested routes to specify the ownership (ie ownerships/2/products/new). current_user will just need to be merged with the params.
If you're trying to create a new ownership and the ownership params in product_params are always going to be the same, then you can just run an :after_create callback which calls a method that creates an ownership.
If not an you need the form for both a ownership and a product at the same time, then that'll be a bit trickier. Let me know if that's the case.
I have a controller for "Productions" and if you go to localhost/productions you see an index page, you can click show and view the show page for that particular productions.
Each production has a unique ID like 036ea872f9011a7c, I want my users to be able to add items to a production like follows:
localhost/productions/036ea872f9011a7c/fixtures/add
localhost/productions/036ea872f9011a7c/positions/add
localhost/productions/036ea872f9011a7c/dimmers/add
localhost/productions/036ea872f9011a7c/channels/add
localhost/productions/036ea872f9011a7c/etc/add
You should build a route with the necessary parameters like this:
Suppose we have tasks that we assign to a project
model project.rb:
class Project < ActiveRecord::Base
has_many :tasks, through: :project_task
end
model task.rb
class Task < ActiveRecord::Base
has_many :projects, through: :project_task
end
routes.rb
...
resources :projects do
member do
get 'affect_task/:task_id', action: 'affect_task', as: :affect_task
end
end
projects/show.haml
= link_to "task_name", affect_task_project_path(task_id: #task_id, project_id: #project_id)
controller.rb
...
def affect_task
...
CollaboratorTask.create(task_id: params[:task_id], project_id: params[:project_id])
...
end
...
Of course this is an example so you understand..
I have followed this tut http://railsapps.github.com/tutorial-rails-bootstrap-devise-cancan.html I want to do something like this:
before_filter :authenticate_user!
before_filter :authenticate_VIP!
before_filter :authenticate_admin!
before_filter :authenticate_somerole!
I have tables: roles, users, user_roles and I don't want to create another table (rails g devise VIP create another table).
I want to have methods authenticate_ROLE. How to do this ?
I have three table, Users, Roles, and RoleRelationships (or role_users, it's up to you)
This is my Role table:
class Role < ActiveRecord::Base
attr_accessible :name
has_many :role_relationships
has_many :users, through: :role_relationships
end
Role table will have name column for roles, like: "admin", "teacher", "vip" (as you want).
And this is User table:
class User < ActiveRecord::Base
devise ...
has_many :role_relationships
has_many :roles, through: :role_relationships
end
and my RoleRelationship table:
class RoleRelationship < ActiveRecord::Base
attr_protected :role_id, :user_id
belongs_to :user
belongs_to :role
end
I set up my app one user can have many roles, you can set up your way. So, i have a role?(role) method in my user.rb, like this:
def role?(role)
return role == RoleRelationship.find_by_user_id(self.id).role.name
end
Then in my abilities files, i define abilities of users:
def initialize(user)
user ||= User.new # guest user
if user.role? "teacher"
can :read, Course
can :manage, Topic, user_id: user.id
can :create, Topic
else user.role? "admin"
can :manage, Course
end
So, teacher will only read Course, and admin can CRUD Course. To do that, i use method load_and_authorize_resource in my CoursesController:
class CoursesController < ApplicationController
load_and_authorize_resource
before_filter :authenticate_user!
...
end
Finally, in my views, i used code like this:
<% if can? manage, #course %>
Only admin can work, see what happen here.
<% end %>
So, as you see, teacher only can read Course so they can't see or do what admin can do, in this case, is create course or edit course.
This is what i built in my online test app, you can reference and do the same for your app.