How to extend model class? - ruby-on-rails

I have two types of users (regular user, super user). What is the proper way to extend one base user class with additional tables?
I was thinking something like this but I am not sure am I going to right direction:
class User < ActiveRecord::Base
end
class SuperUser < User
end
class RegularUser < User
end
Is this the proper way to do it in Rails? Thanks :)

It is 100% correct approach, however you need to remember, that all your models will be stored in one table in database. This approach is called STI (Single table inheritance) and requires only one additional field type in you model.

If you want to have different types of users I would go with user roles versus different user tables etc.
A very good gem for that is CanCan and the documentation is excellent:
https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
You will also have nice helpers as .can? or .cannot? and more.

Yes, and you should also use single table inheritance. What this means is you should add a column called 'type' to your user model. Rails recognizes the column 'type' and treats it special. Essentially, all entries in your type model will reference another model. In that model, you can define rules for each type. It would also be a good idea to validate your user model so that only the two types you want can be entered. This should work:
class User < ActiveRecord::Base
validates :type, :inclusion => {:in => ['SuperUser', 'RegularUser']}
end

Related

Rails STI design - user can be of 2 types

I need some advice on my user model. I came across the STI design and implemented it using devise. This is the basic setup:
class Message < ActiveRecord
has_one :sender
has_one :recipient
# Devise user
class User < ActiveRecord
class Sender < User
belongs_to :message
class Recipient < User
belongs_to :message
My conundrum is that the same user can be both sender and recipient in different scenarios. I originally set up this domain model such that a message record had a sender_id and recipient_id both of which were simply user_ids without any Railsy relationships defined or devise extras.
My previous solution seemed more flexible but the STI design seems more elegant and if possible I'd like to make it work. As I understand it the convention is that the type field discerns which user is returned. Is there a common solution for this using STI?
Broadly speaking, every time I've ended up implementing multiple types of user model, I've ended up regretting it. As you've already noticed, very quickly you end up with people who want to be both types of user, and suddenly you've got to manage different accounts, repeat logins, duplicate emails, etc.
I recommend instead setting up a single type of user that has_and_belongs_to_many roles. For simple cases, you can simply create your own role models and logic (which is what I've usually done), but it looks like this gem is pretty well supported as well: https://github.com/RolifyCommunity/rolify

Extend Model in Rails

I'm trying to extend a model in rails.
Model User uses table users in database with field :username, :password.
class User < ActiveRecord::Base
end
Model SuperUser uses table super_users in database with fields :user_id, :name:
class SuperUser < ActiveRecord::Base
belongs_to :user
end
I would like the SuperUser to be an extension of User so as to be able to do this :
SuperUser.create(:name => "foo", :username => "bar", :password => "foobar")
or when I fetch data to get something like this
> s = SuperUser.find 1
> s.username
> "bar"
Has anyone any idea how I can do this?
You need to consider two possible patterns of extending your models. One of them, Single Table Inheritance is built into Rails and is well documented. The other pattern, the Multi Table Inheritance, which is surprisingly less welcomed in Rails. You need to implement it yourself or use less popular gem for it.
One of the reasons why, I think, MTI is not well supported in Rails is related to performance. For example, even simple User.find 1 should be a lookup in two tables.
What you ask in your question (having two tables) is MTI. But why not STI in this simple case? Are User and SuperUser as deviated from each other that you worry about saving space and using two separate tables for them?
For me it looks like going with a single users table, and adding type column to it.
add_column :users, :type, :string
Now:
class User < ActiveRecord::Base
end
class SuperUser < User
end
and it just works.
You'd want to use either Single Table Inheritance (STI) or Multiple Table Inheritance (MTI). This is not as complicated as it may seem at first. You can read about it here

How to implement a last_modified_by (person) attribute on two unrelated models - Rails

I have a Record model and in order to edit this model, you must be logged in as an instance of Admin. I would like to have a column called last_modified_by which points to the Admin who last modified the Record. In the database, I was thinking it would be good in the records table to add a column that holds the Admin's id; however, the only way I know how to do that is with an association. These two models are not associated with each other so an association doesn't make a lot of sense. Is there any other way I might be able to accomplish this task without resorting to associations? Any advice would be much appreciated!
Hmm, I think the association is a good tool here. You might want to try to hack it somehow but I think nothing you can conjure up will ever be as good as an association via a foreign_key(also so fast). But perhaps you would like to name your association and do something like:
class Record < ActiveRecord::Base
belongs_to :culprit, :class_name => 'Admin', :foreign_key => 'last_modified_by'
end
or give it some more senseful naming?
You could create an Active Record before_save callback. The callback would save the admin's id into the last_modified_column. This would make sure the admin id is saved/updated each time there is a change to the model.
For example, assuming admin is #admin:
class Record < ActiveRecord::Base
before_save :save_last_modified
def save_last_modified
self.last_modified_column = #admin.id
end
As for getting #admin, you could employ a method similar to this, and set #admin = Admin.current (like User.current in the link) somewhere in the Record model.

Single table inheritance with relations

I'm trying to build a student portal in Rails 3, but I'm having some problem.
The idea is to have a users table that contains all basic data for a given person. See the UML/E-R below for example attributes.
A user can be both an Assistant and a Student at the same time.
Assistant and Student should inherit from User.
The idea was to inherit directly from the User, like this.
class User < ActiveRecord::Base
# ...
def awesome?
[true, false].sample
end
# ...
end
class Student < User
has_one :student
has_many :registered_courses, through: :students
end
Student.new.awesome?
This makes the relations in the student model very strange.
has_many :registered_courses, through: :students
I want to be able to do something like this in the end.
student.full_name
student.pin_code
student.registered_courses
One solution would be to implementing the method by hand, like this
class Student < User
has_one :student
def pin_number
student.pin_number
end
end
But it looks really strange to refer to a student object inside the student model.
Is there a clearer, better way of doing this?
Here is an example UML/E-R. I've tried to keep this example clean by removing non relevant attributes. That is why there are so few attributes in the registered course entity.
STI is not a good choice for this the way that you have articulated it here, since users can be both students and assistants. When you are using STI, you generally add a type column to specify which subclass the record really belongs to. If both Student and Assistant inherit from User, then that really isn't an option, since you'd be forced to create duplicate User records for someone who is both an Assistant and a Student.
I think you'd be better off simply having Student and Assistant rows that belong_to a Student, and then delegating the elements that are contained in User back to the User object.
I feel like Inheritance is a bad move here. If you're going to have STI like this it HAS to be one or the other.
Instead throw all your logic into the User model, all your data is there anyway. Plus since Student & Assistant aren't mutually exclusive there shouldn't be any methods that will override each other.
Why not STI?
STI is mainly meant for objects that contain the same data, but does different things with them.
For example, I have a specification that contains multiple processes(ex. build and test). So I have a order that contains processes.
process_1:
order_id: 1
specification: foo
type: build
process_2:
order_id: 1
specification: foo
type: test
In this example the only thing that changes in the data is the type, but because the type changes I know what process to perform from the specification.

Convert model in rails (Single Table Inheritance)

Let's say I have two types of users, A and B. Users of type B have fewer privileges and less-strict validations on fields (more can be blank). Otherwise, they're basically the same as type A. This fact makes me inclined to use single table inheritance.
Here's my concern - type B users can upgrade to type A. When they upgrade, they should keep all their associated records. Is there an easy way to convert from one model type to another using STI such that all associations are preserved? Can you give a simple example?
All you need to do is make sure you have given the correct value in type column.
While upgrading User B to User A, just change the value stored in type attribute to User A's
class name.
Eg:
class Staff < ActiveRecord::Base; end
class PartTimeStaff < Staff; end
part_time_staff = PartTimeStaff.first
part_time_staff.type = "Staff"
part_time_staff.save
it will upgrade the part time staff to staff class.
All associations should be remain unchanged. Since you only have one actual sql table.
All the attributes for PartTimeStaff and Staff class are kept in the same table.
See more details from Rails API and Single Table Inheritance
thanks
Maybe you can have just one User model with a type field, and validations depend on the type.
class User
validates :name, presence: true, if: :advanced_user?
protected
def advanced_user?
self.type == 2
end
end

Resources