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:
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With