My Rails application uses STI where I have two different types of Customers. One is a Person Customer and the other is a Company Customer.
So in my People controller I want to instantiate a Customer. (So that the type attribute of a Person is Customer).
My Customer model inherits from the Person model. The model filename is called customer.rb In my companies controller I also want to instantiate a Customer, which again uses customer.rb, but this won't work because it inherits from Person still.
How can I use the same model, but each model needs to inherit from another model?
#models/customer.rb
class Customer < Person
end
#models/customer.rb
class Customer < Company
end
I tried moving customer.rb to different directories, e.g. person/, company/ but I'm not sure if this is correct. Maybe I should use modules?
That will not work. You cannot have two distinct classes with the same name, and a class cannot inherit from two classes.
Maybe you can use a polymorphic association between Customer and Person/Company.
class Person
has_many :customers, :as => :customer_entity
end
class Company
has_many :customers, :as => :customer_entity
end
class Customer
belongs_to :customer_entity, :polymorphic => true
end
The reason this won't work is that in the example you gave, the second class definition for Customer will change the inheritance from Person to Company, which is not what you want.
If the polymorphic suggestion won't work for you, another solution would be to create two classes, CustomerPerson and CustomerCompany (i recommend using better names!) and extract out any common functionality to a module and then include/extend it into each class.
Related
I have a Users class in my rails app. I need two types of users, 1) Players, 2) Managers. Users will all log in using Devise and have same basic user fields. I will have a League model. What is the best way to model this? STI doesn't seem quite right but not sure if Polymorphic works either since if both Manager < User && Player < User. The only real role a manager will have will be admin roles such as adding/removing players from leagues, setting up schedules etc.
class League < ActiveRecord::Base
has_one :manager
has_many :players
end
class Match < ActiveRecord::Base
has_and_belongs_to_many :players
belongs_to :league
end
class User < ActiveRecord::Base
has_and_belongs_to_many :matches
has_and_belongs_to_many :leagues
end
class Player < User
has_and_belongs_to_many :leagues
has_and_belongs_to_many :matches
end
class Manager < User
has_many :leagues
end
Any suggestions for the best way to set this up are greatly appreciated!
Using STI for players ActiveRecord is looking for Player table for the join but it doesn't exist.
STI can work for this, but since the difference between the types of users is basically a flag that says a user is an admin, you just need something on a user to check whether they are allowed certain actions. Roles can be either exclusive or inclusive (so that a user can be both a Player and a Manager), but once set up you can shape the interface around the capabilities a user has:
<% if #user.has_role?('manager') %>
<%= render 'manager_fields' # or manager_page, etc. %>
<% end %>
How you handle this in the controller depends on how you implement the roles, either via gems like Pundit, CanCanCan, etc. or through explicit checks in something you write yourself. The gems will give you helper methods to reduce the amount of typing involved in views and a more declarative syntax on the back end.
STI eliminates the need of separate tables for each class, given that most (or ideally all) attributes are shared between the two models.
In this case we'd add a new column in the users table called type which will indicate the class that this record will be parsed to, so either Player or Manager
As for the models
class Player < User
and
class Manager < User
As for the permission you could check if the user instance is of type Player or Manager
Of course you don't need to do STI if you don't want to, instead you'll create two tables players and managers and you'll need to join them with the User model, which would be tricky, since not all users have managers and not all users have players, so one or the other will always return nil, .. unless you create a polymorphic relation, but then you'll need to check the type of the object, .. idk
I think STI is your best solution, for the time being
I have a couple of classes that look like this:
Person {
id(PK)
first_name string
last_name string
}
class Employee {
person_id(FK)
job_description string
}
class Student {
person_id(FK)
school_name string
}
If I had a large list of People, how could I figure out what type each of them are without having to do
Student.where(person_id = person.id).any?
and
Employee.where(person_id = person.id).any?
for every "person" in the list?
I need to do a similar operation very often, so would Single Table Inheritance be a better choice?
It looks like what you're trying to implement is Class Table Inheritance.
(See earlier questions. eg. "Class Table Inheritance in Rails 3")
There are various gems trying to implement this,
and they each have their own opinions.
Fundamentally if all you know is the "person_id" then you will always need to look up the other tables to find which class it is.
The simplest way you can do this without changing anything fundamental is to use rails' has_one to create a cached relation between the two tables.
class Person
has_one :employee_details, class_name: "Employee"
has_one :student_details, class_name: "Student"
def employee?
employee_details.present?
end
def student?
student_details.present?
end
end
Importantly a Person can be both an employee and a student.
If that is not the case, then I suggest you look at Rails' Single Table Inheritance, or consider another way of implementing the same.
If it's Ruby on Rails (ActiveRecord)
class Person < ActiveRecord::Base
has_many :employees
has_many :students
end
class Employee
belongs_to :person
end
class Student
belongs_to :person
end
Person.find(person_id).students.any?
Person.find(person_id).employees.any?
Also, for the record, I am just answering the question as you phrased it. I think a better design would be to have a class called (Role) and have Employee and Student extend from that so you could have a many to many association to Role so a person could be both a student(s) and an employee(s) which is entirely possible
I'm developing an application to create a fishbone diagram.
I created several models to handle different levels of causes and effects. The thing is, the application will have one form to introduce data to all the different levels, so how can I do it?
Can I have only one controller to insert info into all the different tables?
Some simple inheritance will do the trick if you are going to be treating them with different logic.
Have a base model
class Bone < ActiveRecord::Base
end
Then three that inherit from it.
class BackBone < Person
end
class RibBone < Person
end
class OutSideBone < Person
end
Then you can handle all three of the classes in the person controller using person as the base. Each will also have their own logic if needed.
If need be you can even do a ownership on to itself, which might be helpful in this case.
class Bone < ActiveRecord::Base
has_many :bones
belongs_to :master_bone, :class_name => "Bone", :foreign_key => "bone_id"
end
I have two database tables, houses and apartments. Each of these has its own view page that I would like users to comment on. Naturally, I also have a posts table. When a post is created it should have a foreign key id referencing the appropriate table (either houses or apartments). Is there an efficient method to create some type of ambiguous foreign key for the posts table and define it on creation? As of now, the best solution to my problem I've thought of is to create two foreign key ids (one for houses and one for apartments) and just have a one column overhead for each post. Thanks for the help!
Single Table Inheritance allows an object to be defined as separate classes while using only a single database table. This is appropriate for objects that are structurally and functionally similar except for slight behavioral differences.
In your case, you have two subclasses, House and Apartment, which are very similar and can be abstracted to a single superclass, Dwelling. This superclass will contain common methods accessible to both houses and apartments. Each subclass will inherit Dwelling and expand it with their own subclass-specific methods. You will instantiate a subclass like normal, but internally Rails will be working with a typecast instance of the superclass. As such, you should never instantiate the superclass directly.
It is worth noting that STI combines all attributes into one table. Attributes for one subclass will be present in another, and are nil if unused. Subclasses with tons of extraneous attributes will result in bloated tables filled with nil values. As a general rule, polymorphic associations should be considered when your subclasses contain more differences than similarities.
Here's how we do. All of this goes in app/models/dwelling.rb:
class Dwelling < ActiveRecord::Base
has_many :posts
# ...common methods...
end
class House < Dwelling
has_many :posts, :foreign_key => "dwelling_id"
# ...house methods...
end
class Apartment < Dwelling
has_many :posts, :foreign_key => "dwelling_id"
# ...apartment methods...
end
In order for this to work, your dwelling table must contain a string column called "type". Rails will see this and automatically know that STI is being used. Note: Both house and apartment tables will no longer be used by the application. Creating a House object will save a new dwelling record to the database, with it's own unique ID and its type will be "house".
When assigning a new Post to a subclass, use the subclass object's ID. Subclass ID and superclass ID both refer to the same record. Note the has_many methods in each of the subclasses. Get a subclass' posts by calling #house.posts.
To infer whether a posting was posted to a house or apartment, you may use #post.dwelling.type.
Edit:
Rails 3's autoloading behavior expects each class to be in it's own file. Therefore, subclasses must be in their own files respectively:
app/
models/
apartment.rb
dwelling.rb
house.rb
Polymorphic Association should do the trick. You want posts to either reference a house and an apartment. You will have to add the polymorphic association via your models and then add the appropriate fields for your tables.
If you do decide to go the polymorphic route this how your models would look.
Posts
#Attributes
housing_type :string #Either will `House` or `Apartment`
housing_id :integer #This will reference the id of House or Apartment, depending on what's in your housing_type field
#Associations
belongs_to :housing, :polymorphic => true
Houses
#Attributes
post_id :integer
#Associations
has_many :posts, :as => :housing
Apartments
#Attributes
post_id :integer
#Associations
has_many :posts, :as => :housing
A basic guide on understanding and creating a polymorphic association.
And of course a RailsCast on it.
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