Rails HABTM and belongs_to for associating users/projects - ruby-on-rails

I have a User and Project model. A user can have multiple projects, and a project can have multiple users. This of course is simple with a has_and_belongs_to_many association. My problem is that I'd also like to track the user who created the project. This would be simple with a belongs_to :user for my Project model. The problem then is that doing a has_many :projects for a User when a user already has a HABTM relationship with a Project makes little sense.
Here's what I would eventually like to achieve:
# building a project with the currently logged in user
current_user.projects.build(...)
# now when a user wants to add another user to this project
project = current_user.projects.find(...)
project.users << User.find(...)
# grabbing information
some_project.user #=> The user who created this project
some_project.users #=> The array of User objects associated
some_user.projects #=> The array of Project objects associated
In the future it's likely a User will have the same sort of relationship with a Group, where a User would both have many and belong to many groups. But again I would also like to track the creator of this Group.
It's quite likely I'm missing something. What's a common way to achieve this?
If it helps to clarify, a possible configuration is laid out below:
Group
has_and_belongs_to_many :users
belongs_to :user
has_many :projects
Only a User should have the ability to create a Group
User
has_and_belongs_to_many :groups
has_many :projects
Can great a group or a project, and can also belong to both
Project
has_and_belongs_to_many :users
belongs_to :group
both a User and a Group should have the ability to create a Project
I've also tried doing simple has_and_belongs_to_many for each association and including a creator_id for example, and tracking this myself. For example.. Project.create(:creator_id => current_user.id) but this seemed hackish and I was sure they would be a better way to do this. Apologies for my ignorance if I've missing something simple.

Reading this again you want to have one owner of the project, either a group or a user. Store that as a polymorphic belongs_to relationship from the project model. ie. owner_type => 'User'/'Group' owner_id => 123
Other users associated with the group are many to many so you need a join table.
Your group model is a separate model with a many to many relationship with users so you can simply store the user who created it as a field of the group model ie. :user_id 123.

Related

Modelling an application in Rails - mixing STI and polymorphic associations

Please forgive me if this has been answered already but I've searched a lot and still require some clarification.
I'm building a Property Management tool using Rails 3.2. I already have a User model and authorisation/authentication code in place with full tests coverage.
I've just started drawing out the models/classes/tables and have gotten myself a bit confused.
Let's start with Users.
Modelling Users
I plan to have allow multiple companies to use this system. Each will have employees (users). These users will have different roles e.g. Manager, Agent, Accountant, Secretary etc. For the most part the data I plan to store for each of these users will be similar (or so I think at the moment) so I am leaning towards Single Table Inheritance and using the type to define the level of access each employee has.
Secondly, I plan to allow Landlord and Tenants to also log in to the system. Upon logging in they'll be able to view information about the property they are owning or renting - maybe keep their contact details up to date too.
I was thinking of using polymorphic associations to represent these users.
So the plan I have at the moment and would like some feedback on is to have
User < ActiveRecord::BASE
Employee < User (this will have a STI type column and allow for the different employee roles)
Landlord < User
Tenant < User
Is this the best way of approaching this problem or am I shooting myself in the foot?
I've had some people advise me I should have a 'roles' table and assign roles to the users - but I have a feeling this isn't the most elegant way to do this in Rails.
Properties
My next issue is with Properties. Right now I have a model for properties and when I add them they belong_to a User (i.e. they have a user_id foreign key). I then started thinking "what happens if the employee (user) that added the Property leaves the company or has their account deleted for some reason?"
So in this scenario is it best to forgo the User/Employee to Property association and just link the Property to the Company that the employee belongs to? This way I can all employee.company.properties to list out all the properties?
Landlord and Tenant associations
Let's presume we make Properties belong to a Company.
In terms of associations this is what I have in my head. Looking at it now I see that everything belongs to a company because one company using the system shouldn't be able to see the landlords/tenants/employees/properties of another company.
class Landlord < User
belongs_to :company
has_many :properties, :through => :ownerships
end
class Tenant < User
belongs_to :company
has_one :property, :through => tenancies #you can only live at one place at a time right?
end
class Property < ActiveRecord::Base
has_many :tenants, :through => :tenancies
has_many :landlords, :through => :ownerships
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :properties
has_many :employees
has_many :landlords :through => :ownerships #not sure if through is required/works here
has_many :tenants :through => :tenancies #not sure if through is required/works here
end
class Employees < User
belongs_to :company
end
Properties
Also I'm guessing we'll have different types of Properties (Commercial/Residential) and of those there will be whole buildings, apartments within a building (single address) etc.
Like with users I'm planning on using Polymorphic Associations to define two subclasses CommercialProperty and ResidentialProperty and then use sti to define a type. If the type is "multi unit" then have a new model for units and an association whereby Property has_many Units and a Unit belongs_to a property.
Aim
I'm trying to make sure that my code follows best practice as much as possible so that when I come to add new features and expand I'm not left having to re-write large chunks of the app.
I would really appreciate your feedback.
Reference
Some of the posts I've read. Hopefully to help others trying to solve the same problem.
Designing a Rails application: single table inheritance?
Ruby on rails with different user types
Ruby On Rails User Model for multiple types
It's probably too late but you could also use has_and_belongs_to_many on User and Company and thus avoid STI altogether by using gems cancan and rolify.
It allows you to define finely grained access rights (abilities).
I know that it seems more elegant having different classes instead of roles, but it is not viable long-term strategy, it can become messy when logic becomes complex.
Other then that, the rest seems pretty solid, hope that helps :)

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

Multiple user roles in Ruby on Rails

I am building an inventory management application with four different user types: admin, employee, manufacturer, transporter. I haven't started coding yet, but this is what I'm thinking.. Manufacturers and transporters are related with has_many :through many-to-many association with products as follows:
class Manufacturer < ActiveRecord::Base
has_many :products
has_many :transporters, :through => :products
end
class Product < ActiveRecord::Base
belongs_to :manufacturer
belongs_to :transporter
end
class Transporter < ActiveRecord::Base
has_many :products
has_many :manufacturers, :through => :products
end
All four user types will be able to login, but they will have different permissions and views, etc. I don't think I can put them in the same table (Users), however, because they will have different requirements, ie: vendors and manufacturers must have a billing address and contact info (through validations), but admins and employees should not have these fields.
If possible, I would like to have a single login screen as opposed to 4 different screens.
I'm not asking for the exact code to build this, but I'm having trouble determining the best way to make it happen. Any ideas would be greatly appreciated - thanks!
Your basic approach seems reasonable. I would advise you to make a base class of User and use STI for specific User types, for instance:
class User < ActiveRecord::Base
end
class Manufacturer < User
has_many :products
has_many :transporters, :through => :products
end
...etc. This way if there's ever the need to aggregate multiple user types into one relationship regardless of type, you have one table to describe Users in general. This is a fairly common approach.
Depending on how much access different users will have to the system, you may want to look at a Role Management gem like Declarative Authorization.
For Multiple user systems, generally preferred ways are - use of role model or STI. If your users can have multiple roles at same time, like single user being Manufacturer and transporter, then Role base system would be good solution. If users role is fixed, then i think you should go with STI.
I suggest you make a User model, Address model, ContactInfo model, etc. You should NOT have those kinds of fields in the User model. Normalize the database. Have a FK in each of those other classes to User.id.
If you MUST keep them separate, then normalize logins and make it polymorphic to reference its owner (manufacturer, employee, etc)

Rails 3 set parameter of belongs_to association

I have a rails app that is tracking social data. The users are going to be able to create groups and add pages(ie. facebook fan pages) to their groups by the page's social id. Since users could potentially be adding the page as someone else, I have it set up so that there is only one page per social id. I also have a pivot table called Catgorizations that links of the pages to the groups and users.
My model relationships are set up as follows:
User
has_many :groups
Group
belongs_to :user
has_many :categorizations
has_many :pages, :through => :categorizations
Page
has_many :categorizations
has_many :groups, :through => :categorizations
Categorization
belongs_to :group
belongs_to :page
Now when I create a new page and it saves, it is creating a new categorization. The problem I'm running into is that in the Categorization I need to set the user_id manually. I've tried:
#page.categorizations.user_id
But I get an undefined method user_id error. I may be approaching this from the wrong direction but how would I go about setting the user_id of a categorization through the page object?
Also if it matters, I'm using devise to handle my user management stuff.
Any help would be greatly appreciated.
Thanks
What you're tring to do is to access something several levels deep in a 'chained' set of relationships.
In order to access an instance of the User model, given a page, you need to get its categorizations, pick one (how?), get its group, then see what the user_id is of that group.
Conceptually a page will actually have many users that might be in charge of categorizing it.
To get something to happen you could arbitrarily pick the first categorization and then do something with its user:
cat = #page.categorizations.first
user = cat.group.user
I don't know what you mean about 'setting' the user id for the categorization - it doesn't have a user, so I don't know what you'll then want to do with that information, sorry!
From your description and your model, only Pages have a User, not Categorization, which is why you get the error that it doesn't exist (it's not in your database).
Also, you are missing the opposite association on Page:
Page
belongs_to :user
This allows you to get back to User from Page:
#page.user.id

Rails has_one :through association

Rails has a has_one :through association that helps set up a one-to-one association with a third model by going through a second model. What is the real use of that besides making a shortcut association, that would otherwise be an extra step away.
Taking this example from the Rails guide:
class Supplier < ActiveRecord::Base
has_one :account
has_one :account_history, :through => :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ActiveRecord::Base
belongs_to :account
end
might allow us to do something like:
supplier.account_history
which would otherwise be reached as:
supplier.account.history
If it's only for simpler access then technically there could be a one-to-one association that connects a model with some nth model going through n-1 models for easier access. Is there anything else to it that I am missing besides the shortcut?
Logic, OK it might sound a bit weak for this but it would be logical to say that "I have a supplier who has an account with me, I want to see the entire account history of this supplier", so it makes sense for me to be able to access account history from supplier directly.
Efficiency, this for me is the main reason I would use :through, simply because this issues a join statement rather than calling supplier, and then account, and then account_history. noticed the number of database calls?
using :through, 1 call to get the supplier, 1 call to get account_history (rails automatically uses :join to retrieve through account)
using normal association, 1 call to get supplier, 1 call to get account, and 1 call to get account_history
That's what I think =) hope it helps!
I'm surprised no one has touched on Association Objects.
A has_many (or has_one) :through relationship facilitates the use of the association object pattern which is when you have two things related to each other, and that relation itself has attributes (ie a date when the association was made or when it expires).
This is considered by some to be a good alternative to the has_and_belongs_to_many ActiveRecord helper. The reasoning behind this is that it is very likely that you will need to change the nature of the association or add to it, and when you are a couple months into a project, this can be very painful if the relationship were initially set up as a has_and_belongs_to_many (the second link goes into some detail). If it is set up initially using a has_many :through relationship, then a couple months into the project it's easy to rename the join model or add attributes to it, making it easier for devs to respond to changing requirements. Plan for change.
Inverse association: consider the classic situation user-membership-group. If a user can be a member in many groups, then a group has many members or users, and a user has many groups. But if the user can only be a member in one group, the group still has many members: class User has_one :group, :through => :membership but class Group has_many :members, :through => memberships. The intermediate model membership is useful to keep track of the inverse relationship.
Expandability: a has_one :through relationship can easy be expanded and extended to a has_many :through relationship

Resources