Best practice to declare different user roles? - ruby-on-rails

I want to declare different user roles on my site and I was wondering what is the best practice to do it in Rails? For now I have two options:
OPTION 1:
I create table Users and declare one string column where I can store names of user roles (SuperAdmin, Admin, Coach, Player)
create_table "users", force: true do |t|
t.string "username"
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "role"
end
Inside User class I save the values like this:
class User < ActiveRecord::Base
ROLES = %w[SuperAdmin, Admin, Player, Coach]
end
OPTION 2:
I create a separate table only for roles. Inside Users table I have integer column for storage of role_id:
create_table "users", force: true do |t|
t.string "username"
t.string "first_name"
t.string "last_name"
t.string "email"
t.integer "role_id"
end
create_table "roles", force: true do |t|
t.string "role_name"
end
class User < ActiveRecord::Base
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :users
end
What would be a better option when if we take search speed, addition of new roles and future maintenance into the consideration?

Basic variant:
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
class CreateRolesUsersJoinTable < ActiveRecord::Migration
def change
create_table :roles_users, id: false do |t|
t.integer :user_id
t.integer :role_id
end
end
end
Here's why: you don't want has_many with roles, cause you won't be able to associate the same role with different users. It is a typical HABTM relationship. Yes, later it can become a performance problem, since it can be pretty hard to fetch all roles with associated records for every user. Then you will look into other variants for optimization: bitmaps, intensive caching or something else.
Hope you find it useful.

Create an other table to store the role is an over engineered solution :)
Use the gem cancan is a good approach, you need just to add a field to the user model.
With cancan you can even assign multiple roles to a user and store it into a single integer column using a bitmask.

IMO the best way is to use a standard has_many belongs_to relationship between a User and Role model:
#app/models/user.rb
Class User < ActiveRecord::Base
belongs_to :role
before_create :set_role
def set_role
role = self.role_id
role_id = "0" unless defined?(role)
end
end
#app/models/role.rb
Class Role < ActiveRecord::Base
has_many :users
end
users
id | role_id | name | created_at | updated_at
roles
id | name | created_at | updated_at
This will allow you to associate users to a specific role, and continue to add / adapt the roles as required

Related

RAILS-4 - has_and_belongs_to_many

I have two models, sellers and customers.
I want it to be a customer has a seller, many customer can have the same seller.
Ideally i want can do customer.seller = seller
With belongs_to association a seller can belongs to jsute one customer.
I use has_and_belongs_to_many association though a in my cas un customer can only have one seller.
# migration
create_table :sellers do |t|
t.string :name
t.timestamps null: false
end
create_table :customers do |t|
t.string :name
t.timestamps null: false
end
create_table :customers_sellers, id: false do |t|
t.belongs_to :customer, index: true
t.belongs_to :seller, index: true
end
# models/seller.rb
class Seller < ActiveRecord::Base
has_and_belongs_to_many :customers
end
# models/customer.rb
class Customer < ActiveRecord::Base
has_and_belongs_to_many :sellers
end
With that i can't do something like that:
customer = Customer.create(name: "John")
seller = Seller.create(name: "Dave")
customer.sellers = seller
I have an error
NoMethodError: undefined method `each' for #<Seller:0x0000000582fb18>
But i can:
customer.sellers<< seller
But if I change the name of the seller like that
Seller.first.name = "Bud"
I want it's to be also modified in my customer.sellers.name.
It's possible to make something like that?
Ok, so for a start, Seller.first.name = "Bud" does nothing to update the database, that name attribute is set on the Seller.first instance which is then lost because you've not assigned it to any variable.
So you'll need to change that to either:
Seller.first.update name: "Bud"
In order to update the DB with the new value, or (more likely) something like:
seller = Seller.first
seller.name = "Bud"
seller.save
That's step 1, actually getting that value saved into the database.
The second problem is that if you have already read customer.sellers from the database then your application already has the value for each of the names stored in memory, you need to reload at least that first record from the DB in order to get the new value:
customer.sellers.reload
And now (I'm assuming that Seller.first is also customer.sellers.first) customer.sellers.first.name will be "Bud"

Rails inherits subclass

I have this three classes user, driver, company.
every company or driver belongs a user. The models look like
class Company < User
has_many :driver
end
class Driver < User
end
class User < ActiveRecord::Base
enum role: [:admin, :support, :B2B , :B2C]
end
and the database looks like
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.timestamps null: false
end
end
end
class CreateCompanies < ActiveRecord::Migration
def change
create_table :companies do |t|
t.string :comp_name
t.string :first_name_counterpart
t.string :last_name_counterpart
t.string :iban_nr
t.string :bic
t.string :email_counterpart
t.string :addresse
t.string :city
t.string :zip
t.references :user
t.timestamps null: false
end
end
end
class CreateDrivers < ActiveRecord::Migration
def change
create_table :drivers do |t|
t.string :first_name
t.string :last_name
t.date :birthday
t.integer :sex
t.integer :dpi
t.integer :score
t.references :user
t.timestamps null: false
end
end
end
Why can't I create a Driver-instance. For example, if I try d = Driver.new, I get a user-instance.d = Driver.new
=> #<Driver id: nil, email: nil, created_at: nil, updated_at: nil>
This is how Rails guesses the table name from the model classes. Quoting from the ActiveRecord docs for table_name:
Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used to guess the table name even when called on Reply.
You should be able to force the proper table name by the table_name= setter, e.g.:
class Driver < User
self.table_name = "drivers"
end
On the other hand, I am also not sure that your approach (with such inheritance) will not cause problems somewhere else.
If you have models with inheritance like you do:
class User < ActiveRecord::Base
enum role: [:admin, :support, :B2B , :B2C]
end
class Company < User
has_many :driver
end
class Driver < User
end
rails infers that you are after Single Table Inheritance (STI) and expects there is just a base table users with a column type which stores the records of User, Company and Driver with actual class name (ex: Company or Driver etc).
If you would rather want to have separate tables users, companies and drivers because each of those tables have different set of columns, and the only reason why you are put inheritance in place is to share some common functionality, then you should extract the common functionality into modules and mix them into those models (by just inheriting from ActiveRecord::Base.
rails, through active_support provides whats called concerns to extract the common functionality into modules and mix them intuitively.
You could probably get away with inheritance and still have these models point to separate tables with the declaration of self.table_name = "table_name". But it is not a good idea, as it goes around the rails conventions and may cause problems down the lane.
Refer to ActiveRecord::Inheritance and ActiveSupport::Concern for more info.

Relation one to many with 2 foreigns keys of the same table Ruby on rails

I have a table called customers. These table has two addresses . One address of work and One direccion of house
Those 2 addresses belong to a table called addresses
I don't know how to relation those 2 tables
Migrations
class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
t.integer :address_id #Address of work
t.integer :address_id_1 #Address of home
t.timestamps
end
end
end
class CreateAdresses < ActiveRecord::Migration
def change
create_table :adresses do |t|
t.string :street
t.timestamps
end
end
end
I do not believe this is a good approach or database design. If you want to proceed this way and not get out of the rails convention just create two columns address_id and address_two_id
and in customer.rb
belongs_to :address, class_name: "Address"
belongs_to :address_two, class_name: "Address"
By default rails takes the name of the foreign key and stores it in a column called "name"+"_id"
The better way is two have a column customer_id in your Address model and create a relation in your customer class
customer.rb
has_many :addresses
And you can also validate that a customer has no more than two addresses by adding this validation to
address.rb
validate :validate_two_addresses
def validate_two_addresses
address_count = Address.where(customer_id: self.customer_id).count
errors.add(:base, "You cannot have more than 2 addresses.") unless address_count < 3
end

Database structure - associations in a multiuser system

I am in the process of building a multiclient system in ROR. (I am looking at http://guides.rubyonrails.org/association_basics.html#polymorphic-associations)
The structure is that a client has a contract, so when he logs in with his username, password and contract, he will have access to the system.
We have the contract id as a “master key”, which has to be in every table in the system.
class CreateContracts < ActiveRecord::Migration
def change
create_table :contracts do |t|
t.integer :contract_id
end
end
end
(chart of accounts)
class CreateCoas < ActiveRecord::Migration
def change
create_table :coas do |t|
t.integer :account_id
t.string :account_name
end
end
end
class CreateCustGroups < ActiveRecord::Migration
def change
create_table :custgroups do |t|
t.integer :account_id1
t.integer :account_id2
t.integer :account_id3
end
end
end
Q1: How do I define the contract with belongs_to? There has to be a relation in every table in the system to the contract table. Do I have to have a relation to all tables? (I think so)
class Contracts < ActiveRecord::Base
has_and_belongs_to_many :Coas
has_many:xxx
belongs:to
end
Q2: How do I define the association on the custgroup? Here we have a record where I have 3 or more fields that link to the same table (COA).
As Jesper said, it's quite hard to follow what you're trying to achieve, but I'll try to reply to your questions :
Q1 : If you want all your tables to reference a contract, you'll need to add to all those tables a foreign_key such as contract_id
so each create_table call will have the contract_id key
create_table :new_models do |t|
t.belongs_to :contract # this will create a contract_id field
end
you can also add an index on the column
add_index :new_models, :contract_id
then in all you models you'll add the belongs_to association :
class NewModel
...
belongs_to :contract
...
end
so if your Coas & CustGroups needs to reference the contract table, you'll have to change both migrations to include the contract_id key and then the models to add the belongs_to association
If a contract needs to have access to all Coas that references it, then you need to use the has_many association
class Contracts < ActiveRecord::Base
...
has_many :coas
...
end
It doesn't look like you need a has_and_belongs_to_many here, but i might be wrong about that.
if a contract also needs to access to CustGroups, you'll add :
has_many :cust_groups in the Contract model.
Q2 : I really didn't get understand what you want to do. Please explain what is the relation between Coas and Custgroups and I'll try to help you

User models with different roles but discrete data?

I'm working on a Rails app where Users can have multiple roles, e.g. Manager, Employee, Admin, etc.
STI doesn't work in this situation, due to multiple roles, but I'd still like to keep role-related data in different tables if at all possible.
So for right now, my schema looks something like this:
create_table :roles do |t|
t.string :name
t.timestamps
end
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email, :default => "", :null => false
t.timestamps
end
create_table :roles_users, :id => false do |t|
t.references :role, :user
end
And my User/Role models both have has_and_belongs_to_many relationships with each other.
So if, for example, I need the Manager to have_many Employees, is that possible with this setup? Is it possible for a User with the Manager role to have a Manager-specific attribute like secret_manager_information? Or do I need to re-think my approach?
Seeing as how Managers need to keep track of Employees (and in general other roles may need to keep track of other special data), I'd say that each role is different enough that they should get their own tables (assuming that you don't have too many roles).
For example, I would create a Manager and an Employee model:
class Manager
attr_accessible :user_id
has_many :employees
end
class Employee
attr_accessible :user_id, :manager_id
belongs_to :manager
end
Any user that is a Manager will have a record in the Manager table with user_id = user.id.
Any user that is an Employee will have a record in the Employee table with user_id = user.id and manager_id = (the id of the corresponding manager record)

Resources