Handling generalization/specialization in CakePHP? - sql

I apologize if I'm using the wrong terms here (please feel free to correct my post or comment). Obviously the SQL isn't super complicated outside of CakePHP, but I'd like learn the correct way to handle this situation with CakePHP.
The situation
I have a generalization/specialization database set up. I've simplified it here (and changed the names to make it clear:
Humans: id, first_name, last_name, hobby
Parents: id, human_id, job
Students: id, human_id, grade
Questions:
My associations should be parents has_one humans, students has_one humans, humans has_one parents and humans has_one students, correct? I always struggle with associations.
How do I then search one specialization model for data contained in the general model using Cake's conventions? So, for example, how would I search only my students for a particular first name?

Answer to question 1:
Your naming is correct (beside that "parents" leads to the modelname "Parent", which is not allowed, because it's a PHP keyword).
The following associations should be sufficient:
Parent belongsTo Human, Student belongsTo Human
Answer to question 2:
Try this in your Student controller (after you created the association in the student model):
$this->Student->find('all', array('conditions' => array('Human.first_name' => 'AnyFirstNameYouWant')));
Otherwise have a look at:
CakePHP doesnt support Multi-Table-/Joined-Table-Inheritance.
You could try the Containable-Behavior:
http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Or the behaviour made for model inheritance with Multi-Table-Inheritance support:
http://bakery.cakephp.org/articles/santino83/2011/02/19/behavior_for_model_inheritance_the_missing_feature

An answer to your question 1:
First notice that since you describe a relational database schema, and not a UML class diagram, you do not deal with "associations", but rather with foreign key dependencies.
As suggested in the comment by Jaaz Cole, for expressing a table hierarchy where humans (better call it people as the plural of person) is a supertable of both parentsand students, you normally declare the primary keys of the subtables to be foreign keys referencing the supertable. So, you should drop your human_id columns.

Related

Understanding PeeWee's related_name property

I'm really enjoying the Peewee ORM. It's lightweight, easy to use, and pretty well documented. One thing I'm having trouble grasping is the related_name property used when implementing foreign keys. I'm never sure whether the property should relate to the table, or to the column. Could someone explain to me exactly what I should be using the name for, and how? For example, with the Student/Courses example found in the Peeewee docs themselves.
https://peewee.readthedocs.org/en/2.0.2/peewee/fields.html#implementing-many-to-many
class Student(Model):
name = CharField()
class Course(Model):
name = CharField()
class StudentCourse(Model):
student = ForeignKeyField(Student)
course = ForeignKeyField(Course)
Assuming I have Student, Course, StudentCourse models. What would the related names be for the StudentCourse columns?
I'm never sure whether the property should relate to the table, or to the column. Could someone explain to me exactly what I should be using the name for, and how?
A foreign key is like a pointer, a 1-to-1. But there is also an implied back-reference -- this is the related name. Examples:
Tweet has a foreign key to the User who tweeted it. The back-reference is the tweets created by a user, so related_name='tweets'.
Category has a foreign key to itself to indicate the parent category. The backreference is the child categories for a given parent, so related_name='children'.
Code snippet has a foreign key to the language it's written in. The back-reference is the snippets for a language, so related_name='snippets'.
For example, with the Student/Courses example found in the Peeewee docs themselves.
That is a many-to-many, and so the back-references aren't quite as "clear" because the foreign keys exist on a junction table. Your frame of reference is the junction table, so the back-references would be studentcourses in both cases, though this isn't helpful because the backreferences just take you to the junction table. So, with many-to-many, generally the backreferences can be left at the default since your queries will usually look like:
# get students in english 101
Student.select().join(StudentCourse).join(Course).where(Course.name == 'ENGL 101')
# get huey's courses
Course.select().join(StudentCourse).join(Student).where(Student.name == 'Baby Huey')

Rails Model class relations in ternary many-many case

I have a 'users' table, 'groups' table and 'invitations' table(join table). I am trying to build a relation between 'invitors'(class_name 'User'), 'invitees'(class_name 'User') and 'groups'(class_name 'Group') where 'invitations'(class_name 'Invitation') is the join table with foreign keys 'invitor_id', 'invitee_id' and 'group_id'.
(Many 'Invitors' can give 'Invitations' to Many 'Invitees' to join Many 'Groups')
I tried several ways by explicitly specifying :foreign_key and :class_name in my Model classes, but in vain. I have just started learning the 'activerecord relations' concepts in rails, and i really want to make efficient use of it. Can someone help me out with this problem.
The problem you are trying to solve here, in ActiveRecord terms, is a "self referential" association. Ryan Bates provides a great example that answers your situation almost exactly:
http://railscasts.com/episodes/163-self-referential-association
In his example you can replace "Friendship" with your idea of an "Invitation". You will need to add a group_id to his Friendship model to keep track of which Group the Invitation is related to.

rails3 and the proper way to use associations

I'm doing my first rails(3) application.
Associations don't make sense. First, even the rails guides don't
really explain what they do, they just explain how to use them.
From what I gather, associations do two things:
a) Allow ActiveRecord to optimize the structure of the database.
b) Allow ActiveRecord to offer an alternate ruby syntax for
joins and the like (SQL queries). I want this.
I'm trying to understand associations, and how to properly use them. Based
on the example below, it seems like associations are 'broken' or at least
the documentation is.
Consider a trivial version of my application. A teacher modifying wordlists
for study.
There are 3 relevant tables for this discussion. For clarity, I've simply
included the annotate(1) tool's definition of the table, and removed
unnecessary fields/columns.
A wordlist management table:
Table name: wordlist_mgmnt_records
id :integer not null, primary key
byline_id :integer(8) not null
A table that maps words to a word list:
Table name: wordlists
wordlist_mgmnt_id :integer not null
word_id :integer not null
We don't actually care about the words themselves. But we do care about
the last table, the bylines:
Table name: bylines
id :integer(8) not null, primary key
teacher_id :integer not null
comment :text not null
Bylines record who, what tool was used, where, when, etc. Bylines are
mainly used to trouble shoot what happened so I can explain to users what
they should have done (and/or repair their mistakes).
A teacher may modify one or more word list management records at a time
(aka single byline). Said another way, a single change may update multiple
word lists.
For wordlist_mgmnt_records the associations would be:
has_many :bylines # the same byline id can exist
# in many wordlist_mgmnt_records
But what's the corresponding entry for bylines?
The Beginning Rails 3 (Carneiro, et al) book says:
"Note: For has_one and has_many associations, adding a belongs_to
on the other side of the association is always recommended. The
rule of thumb is that the belongs_to declaration always goes in
the class with the foreign key."
[ Yes, I've also looked at the online rails guide(s) for this. Didn't
help. ]
For the bylines table/class do I really want to say?
belongs_to :wordlist_mgmnt_records
That really doesn't make sense. the bylines table basically belongs_to
every table in the data base with a bylines_id. So would I really say
belongs_to all of them? Wouldn't that set up foreign keys in all of the
other tables? That in turn would make changes more expensive (too many
CPU cycles) than I really want. Some changes hit lots of tables, some of
them very large. I prize speed in normal use, and am willing to wait to
find bylines without foreign keys when using bylines for cleanup/repair.
Which brings us full circle. What are associations really doing in rails,
and how does one use them intelligently?
Just using associations because you can doesn't seem to be the right
answer, but how do you get the added join syntax otherwise?
I'll try to help your confusion....
A byline can have multiple wordlist_mgmnt_records, so defining the has_many there seems to make sense.
I'm not sure I understand your confusion in the other direction. Since you have defined the attribute wordlist_mgmnt_records.byline_id, any given wordlist_mgmnt_record can only 'have' (belong_to) a single byline. You're simply defining the crows foot via ruby (if you like database diagrams):
wordlist_msgmnt_records (many)>>----------(one) byline
Or read in english: "One byline can have many wordlist_mgmnts, and many individual wordlist_mgmnt's can belong to a single byline"
Adding the belongs_to definition to the wordlist_mgmnt model doesn't affect the performance of the queries, it just let's you do things like:
#record = WordlistMgmntRecord.find(8)
#record_byline = #record.byline
Additionally you're able to do joins on tables like:
#records = WordlistMgmntRecord.joins(:byline).where({:byline => {:teacher_id => current_user.id}})
Which will execute this SQL:
SELECT wordlist_mgmnt_records.*
FROM wordlist_mgmnt_records
INNER JOIN bylines
ON wordlist_mgmnt_records.byline_id = bylines.id
WHERE bylines.teacher_id = 25
(Assuming current_user.id returned 25)
This is based off of your current DB design. If you find that there's a way you can implement the functionality you want without having byline_id as a foreign key in the wordlist_mgmnt_records table then you would modify your models to accomodate it. However this seems to be how a normalized database should look, and I'm not really sure what other way you would do it.

ORM and many-to-many relationships

This is more or less a general question and not about any specific ORM or language in particular: this question comes up regardless of your ORM preference.
When mapping a many-to-many relationship it is possible to obscure the intermediary table or to make the intermediary table a part of your model. In the case that the intermediary table has valuable data beyond the relationship, how do you handle the mapping?
Consider the following tables:
CaseWorker (id, first_name, last_name)
CaseWorkerCases (case_worker_id, case_id, date_opened, date_closed)
Case (id, client_id, field_a, field_b)
As a programmer I would really rather be able to do:
CaseWorker.Cases
than
CaseWorker.CaseWorkerCases.Cases
On the one hand, the table CaseWorkerCases contains useful data and hiding the intermediary table makes accessing that data less than convenient. On the other, having to navigate through the intermediary table makes the common task of accessing Cases seem awkward.
I supose one solution could be to expose the intermediate table in the model and then give the CaseWork object a wrapper property could work. Something like:
public IEnumerable<Case> Cases
{
get{return (from caseWorkerCase in this.CaseWorkerCases
select caseWorkerCase.Case);}
}
But that also seems wrong.
I regard many-to-many mappings as just a notational abbreviation for two one-to-many mappings with the intermediate table, as you call it, enabling simplification of the relationships. It only works where the relationships do not have attributes of their own. However, as understanding of the particular domain improves, I usually find that many-to-many mappings usually need to be broken down to allow attributes to be attached. So my usual approach these days is to always simply use one-to-many mappings to start with.
I don't think your workaround is wrong. The complexities of these models have to be coded somewhere.
I have a blog post about this exact topic: Many-to-many relationships with properties

Should a one-to-one relation, column, or something else be used?

Though my problem is specifically in Ruby on Rails, I'm also asking generally: I have a users table and want to designate some users to be students and others to be teachers. What is the best way to design the schema in this case?
Edit:
Though I originally accepted vonconrad's answer--it fits the original criteria--I have to reopen and adjust the question based on feedback from nhnb.
What should be done if students and/or teachers require additional (unique) attributes?
This greatly depends on how many the different attributes between the teacher and student there'll be as well as how often new unique attributes will be added or removed. If the case is that they will differ a lot, you have two choses:
Make two models, one for Student and one for Teacher and use ruby include to share any logic between the two models. Any associations that associate with a user, you would use a polymorphic association (has_many :user, :polymorphic => true)
Put the attributes in another table. For example: you'll have the users table and a user_attributes table. users would have id, user_type, username, etc. and user_attributes would have id, user_id, attribute_name, value.
If, however, your different attributes between the two users are few and pretty rock solid, you should just consider using Single Table Inheritance.
Good luck!
In this case, I'd simply go with a single boolean column called teacher. If true, the user is a teacher. If false, it's a student. No need to create another model.
If you want more than two roles (say, student, teacher, administrator, janitor(?)), you can add another model called UserGroup. You'll also have to create a column in the users table called user_group_id. In the new model, you have a single row for each of the roles. Then, you specify the following relationships:
class User < ActiveRecord::Base
belongs_to :user_group
end
class UserGroup < ActiveRecord::Base
has_many :users
end
This will allow you to assign each user to a specific group.