Rails multiple roles defined in relationship with a third party - ruby-on-rails

I am building a small app which has classes User, Student, Tutor, Meeting.
Each user can be tutor in one Meeting, but student in another meeting. I have been thinking about how to model these relationship.
Right now, I have a User model, a separate Participant model with an attribute Role = "tutor"/"student". Tutor and Student extend Participant using single-table inheritance, and belong to Meeting.
However, I wonder if this is a good design. (It seems to be unintuitive to me).
What would you do in my situation?
Thank you.

It is acceptable but there are other ways that are cleaner. You could design you Meeting model like this:
has_and_belongs_to_many :users, :as => :studens
belongs_to :tutor, :foreign_key => 'tutor_id', :class_name => 'User'
Your User Model:
has_and_belongs_to_many :meetings, :as => :meetings_taken
has_many :meetings_given, :foreign_key => 'tutor_id', :class_name => 'Meeting'
So you would only need two models.
// edit
For propose, accept, decline,.. I would create a Invitation Model user_id:integer (Invited User), meeting_id(Through this you know who is allowed to invite other people because Meeting belongs to the tutor), status:string (waiting, accepted, delicesed might be options) maybe explaination:text (When somebody delices).

Related

Validate uniqueness through join model in rails

I have a has_many :through association setup between two tables (Post and Category). The reason I'm using has_many :through instead of HABTM is that I want to do some validation on the join table (PostCategory).
So I have 4 models in use here:
User:
has_many :posts
has_many :categories
Post:
belongs_to :user
has_many :post_categories
has_many :categories, :through => :post_categories
Category:
belongs_to :user
has_many :post_categories
has_many :posts, :through => :post_categories
PostCategory:
belongs_to :post
belongs_to :category
Basically what I want is: Users can create posts, users can also create their own categories. A user can then categorize posts (not just their posts, any posts). A post can be categorized by many different users (in different ways potentially), and a category could contain many different posts (A user could categorize N posts under a specific category of theirs).
Here's where it gets a little bit tricky for me (I'm a Rails noob).
A post can ONLY belong to ONE category for a given user. That is, a post CANNOT belong to more than ONE category for any user.
What I want to be able to do is create a validation for this. I haven't been able to figure out how.
I've tried things like (inside PostCategory)
validates_uniqueness_of :post_id, :scope => :category_id
But I realize this isn't correct. This would just make sure that a post belongs to 1 category, which means that after one user categorizes the post, no other user could.
Really what I'm looking for is how to validate this in my PostCategory model (or anywhere else for that matter). I'm also not against changing my db schema if that would make things easier (I just felt that this schema was pretty straight forward).
Any ideas?
The simpliest way is to add user_id to PostCategory and to validate uniqueness of post_id with user_id scope.
Another way is to create custom validation which checks using sql if category owner has added category to that post.
Option 1 : use a before_save. In it, do a SQL look up to make sure a post with a similar category for your user doesn't exist (take care that on edit, you'll have to make sure you don't look-up for the current Post that is already in the DB)
Option 2 : custom validators :
http://guides.rubyonrails.org/v3.2.13/active_record_validations_callbacks.html#custom-validators
Never used them, but sounds like it can do what you want

Rails employer and employee devise

I am trying to figure out how to structure this please help:
I need users to be able to sign up as either an employer or employee. The employer basically is the company and the employees are the people employed at that job. I want to know how to keep a record of when/if the employee was hired and terminated from the job. Would a has_many:through assotiation work for this or better yet is there a gem that I am overlooking that could help me with this?
This is a classic case of the hierarchical object model, so you have to think about this problem in terms of the objects involved and their relationships to each other. Think about the way a business works in real life:
Employer -> Employees NOT Employer -> Manager etc. -> Employees
A good example of this system model is that of GitHub. On GitHub, users can belong to Organisations. They can also create them and administrate them, managing members etc. (hiring and firing in your case). Therefore a much better way of modelling this system is having it be User-centric rather than a split of two different classes of User.
As stated in a previous answer, Employers (or Businesses in this
sense) should not be considered as users of the system as they won't
be acting on the state of the program, users will. Therefore, I personally think STI is a
little overkill for this system model.
So...
All people employed in a business are employees, but not all employees have the same level of authority (Managers will have more authority than Junior employees for example). Therefore you should model a Business, which has many Employees. Those Employees will have varying levels of authority based on their position:
can_hire?
can_fire?
etc.
You could also create a method that would tell you when this Employee was hired/fired. These could return a date/time if they were hired/fired or nil if not. nil in this sense would obviously imply they haven't yet been hired/fired.
When it comes to managing the abilities of different users, their level of authority is defined by what they can and cannot do. You can of course apply preset roles to this situation with a simple method that checks the above methods. For example:
def manager?
self.can_hire? and self.can_fire?
end
You would then also define an ability that would allow the creation of a Business:
def can_create_business?
self.manager?
end
Note: you can use scopes to achieve this elegantly.
You can also sub-class a basic User model for your roles, or create a Role class that defines the above methods and acts on a given User model. You can then create individual roles and subsets of roles.
Arguably, there are some cases where allowing the creation of employers/businesses and employees as separate entities that can both act on the state of the program are useful. If the Business can perform more than just simple administration functions then this might be worth it. In which case, I would still not consider the business as a User model. I would require a special user account be created along with the Business which would have a role of Business Administrator or something similar.
This model follows some of the most basic principles in programming and Computer Science as a whole, so I hope it sheds a little light on your problem. Of course there are a number of gems available that have this functionality built-in, namely Devise and CanCan/CanTango, although I would personally build this system myself.
I think I will combine STI with has many through relationship.
I will first start by making a single table inheritance of Employers and Employees.
class User < ActiveRecord::Base
...
end
class Employer < User
end
class Employee < User
end
Employer hires employees. One employer can have many employees and with employment come other related properties like date_of_hire, designation, status, manager_id, department etc. Hence I would model this as a separate object and hence store this information in a separate table. Let's call that Employment, shall we?
rails g model employment employer_id:integer, employee_id:integer, date_of_hire:date, designation:string, status:string, manager_id:integer
Let's establish relationships now.
class Employment < ActiveRecord::Base
belongs_to :employer # Foreign key options should not required because of User STI
belongs_to :employee
belongs_to :manager, :foreign_key => :manager_id, :class_name => "User"
end
class Employee < User
has_many :employments
has_many :employers
has_one :manager, :through => :employments, :foreign_key => :manager_id
end
class Employer < User
has_many :employments, :foreign_key => :employer_id
has_many :employees, :through => :employments
end
Based on business rules we can implement elegant scopes.
class Employee < User
...
scope :fired, :where => {:status => 'fired'}
scope :recently_joined, :where => {:date_of_hire => 3.months.ago...Date.today}
scope :developers, :where => {:designation => 'developer'}
...
end
and...
Employee.recently_joined
Employee.recently_joined.developers
Employee.fired
Please understand that this is obviously not a tested code and may have some glitches.
However!
I would highly encourage you to re-consider the need of modeling employers as users. From my personal experience, it turned out to be disaster in the future (May be I was inexperienced then but I won't really take that route again). I would really make separate models for Employer and Employee and establish relationships as above but with foreign_key and class_name attributes. The major reason is that for STI to work, in your domain, the employer and employee should have an "IS A" relationship with User. You might be tempted to think that it is an "IS A" relationship but also think whether it's the same "TYPE" of "IS A" relationship. In the application I worked on, it did not make sense and yet we settled with STI. The employers had completely different set of features, identity and treatment in the application from the employees. Although there was some data that was the same for both, it existed for different purposes and was used different. That is a reason enough (Single Responsibility) to model them separately. STI is a tricky tool that can solve a problem. But use it incorrectly and it will create more problems than what it would solve.
Just use has_many, :through. It will get the job done. Say you need to get all the active employees for a single company you can do
class Employers
has_many :hired_employees, :through => :employments,
:class_name => "Employees",
:source => :employee,
:conditions => ['employment.status= ?',"hired"]
end
Then you can do Employers.first.hired_employees. You can add more and use different conditions so that you can get 'terminated', 'dismissed' etc.
Of course this assumes you have a third model called Employment where it belongs to Employee and Employer. Your Employee and Employment classes could look like this:
class Employee
has_many :employments
end
class Employment
belongs_to :employee
belongs_to :employer
end
From what you said, you may need the following model: User, Job, Assignment, Role
class User
... some devise declarations here ...
... some role here. rolify gem is my suggestion ...
scope :employee, :where => {user.has_role? :employee}
scope :employer, :where => {user.has_role? :employer}
has_many :jobs, :through => :assignments
has_many :assignments
end
class Job
belongs_to :user # employer
has_many :assignments
has_many :users, :through => :assignments # employee
end
class Assignment
belongs_to :job
belongs_to :user # employee
def fired?
... stuff to check if the employee is fired
end
def hired?
... stuff to check if the employee is hired
end
end
Pros:
you can add more roles such as manager, admin ... easily. Using rolify gem or you can just create the role model for yourself
the assignment class will stored anything you need to store such as fired, hired about the employee
since the roles are clearly predefined, access control can be added easily using authorization gem such as cancan.
Cons:
Using this model, use must check whether the current user you are working on is an employer or an employee before doing anything else. However, this can be easily be checked using roles
For simplicity, you can use rails_app_composer gem to generate the initial app with devise, cancan and rolify installed.
NOTE: this answer is based on many similar questions such as:
Polymorphism or STI

Rails naming conventions when having two relations to the same model

My question is more related to naming conventions than programmation I guess.
Let's assume an application where users can create new articles (so they are the owner of these articles) and where you can add article "editors", who can only update the article content.
class User
include Mongoid::Document
has_many :articles # as owner of the articles
has_and_belongs_to_many :articles # as editor of the articles
end
class Article
include Mongoid::Document
belongs_to :user
has_and_belongs_to_many :editors, :class_name => 'User'
end
What I want to know is how I should call the articles association in my User model. I mean, an article has an author and editors, which seems strong naming conventions to me, but a user has articles he created and articles he is the editor. How would you call/name/declare the last 2 associations?
I would call them as :edited_articles, and :authored_articles or :owned_articles, or something similarly straightforward names. Just dont forget to add the :class_name and :foreign_key or :through qualifiers to them.
Update:
For has_and_belongs_to_many relation you need a connection table, which is by default, is named of the two joined table. E.g. articles_users in your case. In this table you will propably have two ids, user_id and article_id. This way rails connects your models automatically.
has_and_belongs_to_many :editors, :class_name => 'User', :foreign_id => 'user_id'
Of course if you call it editor_id in the join table then use that. And the opposite, on the user side should work too.

Linking 2nd row to different database table, in rails

lets say I have the Users table, and the Team table.
In Rails, I know how to link the user_id column in the Team table to the Users table. But what if I have a second column I also want to link to the user's table, such as user_id2 (this essentially creates an order in the team table)?
Is there a solution, or something I don't know about to do what I'm trying? I also don't think the "has_many" is what I'm looking for, because user_id might be the team manager, and user_id2 might be the team captain, i.e. they have different roles affiliated with them, and order is important.
Thanks!
Edit: for my purposes, I also wouldn't need more than these two user relations. (i.e. cases for three wont be relevant)
You may want to look into STI (look for Single Table Inheritance on that page) or Polymorphic Associations. Either would allow you to express your intent a bit more clearly, although there isn't enough information in your question for me to puzzle out which would fit best.
Give those a read and see whether they accomplish what you want.
First here is a way to do this in one direction (Team -> User), but it wouldn't work for the reverse direction, and there's a better option I'll get into afterwards. The first one assumes you have columns named manager_id and captain_id on the teams table.
class User < ActiveRecord::Base
end
class Team < ActiveRecord::Base
belongs_to :manager, :class_name => ::User
belongs_to :captain, :class_name => ::User
end
However, I'd be surprised if a Team only consisted of two Users (the manager and captain) - it's more likely that you'd want a join table to track all of the users' team memberships. That join table (called team_memberships in this example) could have a role column that holds the manager/captain info, as well as any other data you have. This way is a lot more flexible, and offers additional benefits, like being able to track historical team data if team members change over time, which they will.
class Team < ActiveRecord::Base
has_many :team_memberships
has_many :users, :through => :team_memberships
def captain
team_memberships.captain.first
end
def manager
team_memberships.manager.first
end
end
class TeamMembership < ActiveRecord::Base
belongs_to :user
belongs_to :team
# You'll need some database-level UNIQUE INDEXes here to make sure
# you don't get multiple captains / managers per team, and also
# some validations to help with error messages.
named_scope :captain, :conditions => {:role => "captain"}
named_scope :manager, :conditions => {:role => "manager"}
end
class User < ActiveRecord::Base
# depending on the rules about teams, maybe these should be has_many...
has_one :team_membership
has_one :team, :through => :team_memberships
end
Check out http://guides.rubyonrails.org/association_basics.html for more details.

user has many :users, or must I use another way for a friend based social network?

I'm creating a little social network in Rails, where people can add eachother as a friend. I have created a model called 'user', which contains, e-mail, strong md5 hash with salt of password etc.
How do I create something like an option to add another user as a friend? Is it possible to have something like has_many_and_belongs_to :user in the user model? So a user has many users and belongs to many users. Or should I use another way, like adding a friendship model which has user1s_id:integer and user2s_id:integer?
Essentially you want a join model that joins users to users. But you'll have to use more descriptive terms for Rails.
Twitter doesn't use Rails, but here's how their user associations might work in Rails using Twitter's terminology. Note: Twitter doesn't require bidirectional following (ie just because a user follows another, doesn't mean that second user follows the first)
class User < ActiveRecord::Base
has_many :followings
has_many :followers, :through => :followings, :class_name => "User"
has_many :followees, :through => :followings, :class_name => "User"
end
class Following < ActiveRecord::Base
# fields: follower_id followee_id (person being followed)
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
Forced bidirectional friendship (like Facebook enforces that I cannot be friends with you, unless you are a friends with me) will take a little more work. You will either need to manage reciprocal entries with callbacks, or use custom finder SQL. Using custom finder SQL means that ActiveRecord probably won't be able to manage associations for you.
I would suggest that a user has many relationships, this would leave you free to add new types of relationships in the future.
So you start with a "one user to another user" relationship table (minimally, just two IDs) and then in the future you could add a "one user to a group" relationship table (once you've created groups of course!)
I'd suggest using the friendship model because db-wise you'll need a join table either way, but making that table explicit will allow you to store more details about the relationship (e.g. "friend" or "family", date added, ...)

Resources