I wonder what the best practice is to handle teams with different sets of active records, through the whole database.
Lets say we have a app which is used by different teams. Every team can access all the active records belonging to them but not the active records belonging to a other team.
The primitive way would be
add a team_id to every record
in every create add the team_id of current user to active record
to every query add where(team_id = current_user.team_id)
use pundit or equal gem for permissions
I wonder if theres a better way. For example, in the model add the team_id with after_initialization.
Is it possible to automatically add team_id = current_user.team_id to every query? What would be the best way to handle that? Is there a gem?
"Every team can access all the active records belonging to them", that is your answer, in my opinion. All record should belongs to a team. Pundit is designed to build policies about actions (with or without roles), you need the team id to know from where the records comes so Pundit is not your best option. This is what I would do
class Team < ApplicationRecord
has_many :users
has_many :notes
end
class Note < ApplicationRecord
belongs_to :team
end
class User < ApplicationRecord
belongs_to :team
end
After login you will have the current_user so you have the team too
class ApplicationController < ActionController::Base
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
def current_team
return unless current_user
#current_team ||= current_user.team
end
end
then you can chain associations (or use delegates) to get your records
class NotesController < ApplicationController
def index
#notes = current_user.team.notes
end
end
or
class NotesController < ApplicationController
def index
#notes = current_team.notes
end
end
I hope it helps.
Related
Trying to figure out the best way of organising users into organisations so that the members of each organisation only see items added by themselves and other people from their organisation. All users need to use a shared login.
I have some previous experience with Devise so would like to use it if possible.
What is the best way of tackling this issue?
Any links to tutorials explaining this would be great.
Thanks
Let's assume you have user model that belongs to some company:
class User < ActiveRecord
belongs_to :company
....
end
company model has many users and has many products(items):
class Company < ActiveRecord
has_many :users
has_many :products
end
product model that belongs to company:
class Product < ActiveRecord
belongs_to :company
....
end
In your case, what you're trying to achieve is that user can see/edit only products from his company. Using before_action in your controller you can define what user can/cannot do. Here is the basic example:
class ProductsController < ApplicationController
before_action :authorize_user, only: [:show, :edit]
def show
end
def edit
end
private
def authorize_user
raise "Not Authorized" unless current_user.company_id == #product.company_id
end
end
This way you will raise an error whenever user try to access the product that doesn't belongs to his company.
Ofcourse, this is just basic example, you should take a look on Pundit gem which is very powerful, but you should get the point with this :)
Let me know if I missed something.
Cheers
I have a signed in user profile and each profile has its own phone-book that no other user can access. The question is how should i implement it. Considering User as one controller and phone-book as another i'm not able to establish a relation between the two for a specific user login.
What should be my approach?
I have a sparate model for User and separate model for phone-book and have established a relation between them using has_many and belongs_to macro.
Let's start with the models. You say that each User has only one PhoneBook so I would say that the right models should rather be:
class User < ActiveRecord::Base
has_one :phone_book
end
class PhoneBook < ActiveRecord::Base
belongs_to :user
end
Now, about the controllers.
When you have a signed in User you will eventually have a "session thing" going on.
Let's say you're using devise, then you will have a variable current_user that references the logged in user. So the PhoneBooksController will be something like:
class PhoneBooksController < ApplicationController
def index
#phone_book = current_user.phone_book
end
end
Of course if your users can have more than one PhoneBook we go back to the has_many association:
class User < ActiveRecord::Base
has_many :phone_book
end
class PhoneBook < ActiveRecord::Base
belongs_to :user
end
and the controller becomes:
class PhoneBooksController < ApplicationController
def index
#phone_books = current_user.phone_books
end
def show
#phone_book = PhoneBook.find_by_id(params[:id])
end
end
At last, if you want these phone books to be publicly readable I suggest you stick with a REST kind of URI
/phone_books/:id <-- good
/users/:id/phone_books/:phone_book_id <-- too complex
Hope I could help
You might want to place the page in /users/:user_id/phone_books/:id.
To achieve that,
You have to configure the paths in config/routes.rb:
resources :users do
resources :phone_books
end
And in app/controllers/phone_books_controller.rb, find the user and their address book:
class PhoneBooksController < ApplicationController
before_action :find_user
def show
#address_book = #user.address_books.find(params[:id])
end
private
def find_user
#user = User.find(params[:user_id])
end
end
For more information about nested resources, please see the Getting Started with Rails guide.
I'm trying to build a multi tenanted app in which which different banks are separated by subdomain. This part is working fine. Now there is one more level of multitenancy for bank products.
Each bank has multiple products
A devise user can belong to only on product
This means that you will have to register twice for two products of the same bank even though they are under same subdomain(client requirement can't change)
Because of this you can have same email address for two products. Uniqueness is scoped to product_id
So I have to select a product while signing in and signing up
This is how I'm trying to implement above solution
around_filter :scope_current_bank, :scope_current_product
before_filter :authenticate_user!
helper_method :current_bank, :current_product
def current_bank
#current_bank = Bank.find_by_subdomain!(request.subdomains.first)
end
def current_product
if user_signed_in?
#current_product = current_bank.products.find_by_id(params[:product_id])
else
#current_product = current_user.product
end
end
def scope_current_bank
Bank.current_id = current_bank.id
yield
ensure
Bank.current_id = nil
end
def scope_current_product
Product.current_id = (current_product.id rescue nil)
yield
ensure
Product.current_id = nil
end
Now the problem is while user is sigining in, the scope_current_product method calls user_signed_in?, obviously it fails because product_id is nil. Now it enters the else block after which I expect it to call authenticate_user! as its a before_filter but it does not happen as authentication was already done. So I get a message saying authentication failed.
Is their any way to call authenticate_user again?
Although not a direct answer, hopefully this will give you some ideas:
Authorization
Perhaps you should look at - Is there a difference between authentication and authorization? - there's a good RailsCast about this
I think your issue comes down to the idea you need to authenticate the user once (login / logout), but should then authorize that user to work with different resources
Code
A devise user can belong to only on product - I would recommend this:
#app/models/product_user.rb
Class ProductUser < ActiveRecord::Base
belongs_to :product
belongs_to :user
end
#app/models/product.rb
Class Product < ActiveRecord::Base
has_many :product_users
has_many :users, through: :product_users
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :product_users
has_many :products, through: :product_users
end
This is a typical has_many :through association:
#user.products
#product.users
CanCan
It means you can use CanCan to do something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user
can :manage, Product, users.exists?(user.id)
else
can :read, :all
end
end
end
This allows you to control which products the user can edit / access. Obviously my code needs to be tweaked, but I hope it shows you the value of authorization over trying to do multiple authentications
I am trying to figure out the best way to accomplish my problem. I've got a pages table, and a user_types table. I am trying to specify multiple user types on a page. They will act as permission groups. I need to do this twice however. Once for read permissions, and once for edit permissions. Here is an example:
Home page has 3 user types that can read it - admin, super admin, public
It has 2 user types that can edit it - admin, super admin
I have one user_types table:
admin
super admin
public
etc
I have created two mapping tables (one for read, and one for edit):
pages_user_read_types
pages_user_edit_types
they both have page_id, and user_type_id
Is there a better way to accomplish this? If this is the best way, I need help figuring out the relationships for the models. I have this for one relationship
has_and_belongs_to_many :user_types, :join_table => :pages_user_read_types
How do i specify two relationships for seperate fields?
Thanks
The HABTM relationship in Rails has seemed to fall out of favor over the last couple of years with Rails developers to the has_many :through relationship. The only time you should use HABTM is when you have no need for any additional information about the relationship between two models. In your case, you are trying to emulate this by creating two HABTM relationships when you could effectively accomplish by having a join model with a editable attribute.
In code, it would look something like this:
class Page < ActiveRecord::Base
has_many :page_permissions
has_many :user_types, :through => page_permissions
def editable_user_types
page_permissions.includes(:user_types).where(:editable => true).map(&:user_type)
end
def read_only_user_types
page_permissions.includes(:user_types).where(:editable => false).map(&:user_type)
end
end
class PagePermission < ActiveRecord::Base
belongs_to :page
belongs_to :user_type
# When you create this model, you should have a boolean attribute for editable
end
class UserType < ActiveRecord::Base
has_many :page_permissions
has_many :pages, :through => :page_permissions
end
I think following this approach will allow you to consolidate to one join table which will be better in the future if you need to add additional attributes to the relationship (PagePermission) between Page and UserType.
At the very least, you probably want to add a Permission model. If it ever gets more complicated than what you've described, I would also recommend using CanCan.
class Permission < ActiveRecord::Base
#table is id, page_id, user_type_id, and permission_type (string).
belongs_to :page
belongs_to :user_type
end
In your controller, you can construct a filter chain like this:
class PagesController < ApplicationController
before_filter :load_page
before_filter :authorize_view!, only: [ :show ]
before_filter :authorize_edit!, only: [ :edit ]
def show
end
def edit
end
private
def load_page
#page = Page.find(params[:id])
end
def authorize_view!
if !#page.permissions.where(user_type_id: current_user.user_type_id, permission_type: "view").exists?
flash[:notice] = "You do not have permission to view that page."
redirect to root_path
end
end
def authorize_edit!
if !#page.permissions.where(user_type_id: current_user.user_type_id, permission_type: "edit").exists?
flash[:notice] = "You do not have permission to edit that page."
redirect to root_path
end
end
end
(This assumes you have a current_user method in your app).
A User can only have two types of Subscriptions: DailySubscription and WeeklySubscription. When the user is at the new and edit action, I'd like them to check off either of the subscriptions they would like to get.
I'm comfortable using nested fields (as per Ryan Bates' screencast here) but I think when I add inheritance, it really complicating matters. Is there a better way?
class User < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
# type field is defined in the migration for Single Table Inheritance
end
class DailySubscription < Subscription
# Business logic here
end
class WeeklySubscription < Subscription
# Different business logic here
end
My initial efforts with the controller are wacky:
class UsersController < ApplicationController
def new
#user = User.new
# I can't use #user. subscriptions.build as Rails doesn't
# know what type of model to add!
#user.subscriptions = [DailySubscription.new, WeeklySubscription.new]
end
...
end
I think I am conceptually really missing something here but I can't figure it out. Help!
Judging from your description, your user has only two possible subscription choices: daily and/or weekly. Therefore you dont need to have a has_many association because two has_ones would suffice(note polymorphic subscribeable below:
class User < ActiveRecord::Base
has_one :daily_subscription, :as => :subscribeable
has_one :weekly_subscription, :as => :subscribeable
end
class Subscription < ActiveRecord::Base
belongs_to :subscribeable, :polymorphic => true
# type field is defined in the migration for Single Table Inheritance
end
class DailySubscription < Subscription
# Business logic here
end
class WeeklySubscription < Subscription
# Different business logic here
end
furthermore for the controller you just need to initialize User. Upon initialization, #user.daily_subscription and weekly_subscription will be null as determined by .blank? method. When you go ahead and create the user in the create method, you will need to populate these fields with instances of corresponding subscriptions.
class UsersController < ApplicationController
def new
#user = User.new
# bam -- youre done.
end
...
end