Is many-to-many association still apply here? - ruby-on-rails

When an entity isn't associated with many other entity at the same time, should I still use many-to-many association?
For example, I understand that Author-Book relationship is a many to many, An author can write many books, a book can be written by many authors. This holds for all time.
Consider this situation. A Batch has many students at one particular time. If a student fails, he should move to the next junior batch. In other word, A student cannot belong to more than one batch at the same time. In this case, is this a many to many association?
Alternative Solution I thought:
I was thinking about putting two columns in students table. initial_batch and current_batch. I can get the student's Batch history, by checking the gaps between current and initial batch, because of the rule A student must drop to the next immediate batch. Also, batch.students exists with a has_many association in rails. So, I think that would not be a big deal.
By Batch, I mean batch of students get admitted in the same year.

I think you'd still want to use an association table (many-to-many association) because you might want to keep a history of which classes the student has taken.
If you want it to be a many-to-one and have a student belong to one class at a time, you'd have to add a class_id field to your student table. This allows you to call student.class to find their current class, but you wouldn't be able to do class.students because that relation doesn't exist. For this reason, I think it should be a many-to-many.

is this a many to many association
Yep.
An ActiveRecord association is simply a way for you to create two connected objects (ActiveRecord is an ORM -- Object Relationship Mapper).
Remember, as Ruby is object orientated, each "Model" (class) is counted as an object, invoked every time you want to populate it with data. Each ActiveRecord association for each object is accessed through a method in the object...
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :subjects #-> AR appends the "subjects" method to your Student object
end
Simply, this means that even if you have the functionality for a number of different objects, you don't need to have the method populated with data.
If you have the following...
#app/models/student.rb
class Student < ActiveRecord::Base
has_and_belongs_to_many :subjects
end
#app/models/subject.rb # "class" is a reserved word
class Subject < ActiveRecord::Base
has_and_belongs_to_many :students
end
This simply provides the functionality / capacity for #students.subjects etc.
What's contained inside this method is up to you to determine:
<% if #students.subjects.any? %>
<% #students.subjects.each do |subject| %>
...
<% end %>
<% end %>

Related

Looking for a way to track history in rails database

I'm considering this an add-on question of sorts to the thread below:
Using join tables in ruby on rails
So we have 'Student' and 'Course' scaffolds joined by a many-to-many association, but in addition there is also a 'Semester' scaffold and what I wish to do is, for each student that is added to a course, for the application to search for previous iterations of the same course through past semesters, to that it's known how many times a student has taken that class before. I'm kind of mixed up at the moment as to how to implement this, so I was hoping someone could help me pin down the logic and code I should be operating by.
Some underlying assumptions I have so far:
'Course' and 'Semester' should, like 'Student' and 'Course', be joined
by a many-to-many association (many courses are taught per semester,
and a course is taught for more than one semester).
There should be an action (let's say get_student) within the course
controller to locate the student via student_id. This would be the main area I'm scratching my head as to what to do. What would this method look like in code?
Within the student-course join table I should have an attribute
'attempts' which increments each time get_student finds this
student_id combined with the course_id that calls the method.This
would be the mechanism that actually tells how many times the course
had been attempted by the student.
I initially wondered if there should be a 'semester' controller
action to activate get_student across all semesters, but now I'm
thinking that get_student should work fine without that.
Appreciate any help I can get on this. Thanks.
This is not a good answer, just a comment.
I would comment, but hear will be more clear. I ll update for the other points. This is just an ongoing feedback/discussion, not an answer.
class Semester < ApplicationRecord
has_many :courses
end
class Course < ApplicationRecord
has_many :students
end
And
semester.courses[0].students => outputs the students array for that
This could be the method to calculate the number of student that did that course:
def studentForCourse
#input_params.course_id => course id you are selecting
semester = Semester.find(input_params)
semester.courses.each do |course|
if course.id = input_params.course_id
nstudents = course.students.size
end
end

Rails - Creating and Validating Many-to-Many relationship with the same model?

This is probably a really simple question, but I've been searching the web for probably around an hour and I can't really find an answer to my problem. It should be clear by what follows that I am very new to Rails, so my terminology and explanation might be a bit confusing.
Let's say that I were making a social media app on Rails, where one of the models is User. I want to make a many-to-many relationship called "friends", which links two users together. Let's say in this situation I also wanted to make a many-to-many between two users called "enemies".
This is all completely hypothetical, but the idea is the same one that I want to use for something I'm working on.
Because a user can have many friends and enemies, but also be many friends and enemies, I would use:
class User < ActiveRecord::Base
has_and_belongs_to_many :users #this should be the friends association
has_and_belongs_to_many :users #this should be the enemies association
end
Now I'm guessing I can't just do that, because I would have to have two tables both named users_users. So, then I switch to:
class User < ActiveRecord::Base
has_and_belongs_to_many(:users, join_table: 'friends',
foreign_key: 'user_id', associate_foreign_key: 'friend_id')
end
With a similar statement for the enemies table. Now, my problem is that I want to have a form that the user can use when they sign up, where they can input their information (this is the User object details), and also list their friends and enemies.
Because the user won't have the database id key for their friends or enemies, they'll have to input the users' names. This is fine, though because the name is also a unique key, guaranteed by the validation.
However, if the user types in the name of a friend, I can't join the two if the friend happens to not exist. So, I use a custom validation class that looks something like this:
class FriendValidator < ActiveModel::Validator
def validate(object)
#lookup user and throw error if not found.
end
end
which will access the variable (object.friends) and (object.enemies)
With something similar for enemies. So therefore, above my has_and_belongs_to_many statements, I have lines that say:
attr_accessor :friends, :enemies #these are attrs because they don't exist within the model's db
validates_with FriendValidator
When I create the form with erb, I have the standard form_for block
<%= form_for(#user) do |f| %>
It seems to me that I can't just stick
<%= f.text_area :friends %>
because friends isn't actually something that will get passed to the User object, but rather a separate table. (Can I, though? Because the attr_accessor is declared in the user's model class?)
So now, we have my main problem. I have two many-to-many tables with a model to its own model class, and I don't know how to ensure that the validation class will take the two attributes, lookup and throw necessary errors, and then add a row to the join tables using the id of the user, rather than the string inputted. What form fields should I use to pass the input to the right place? Where do I change the controller methods so that the input gets sent to the join table rather than the user object?
This definitely seems like a pretty specific situation, so I can't really find an answer in the Rails documentation, which I've been learning from.
My initial impression of this problem has to do with your associations. To me, a user has_many enemies and has_many friends.
friends belong_to user
enemies belong_to user
Not sure if a many to many relationship makes sense in this case. Maybe that's why you are having such a hard time finding an answer online. Just my two cents.

Validate presence of associations in a many-to-many relation

In my Rails app I have a many-to-many relationship between 2 models Teacher and Course through a join table. I'd like to create some sort of validation where a course can't be created without being associated to at least one teacher (it is assumed that all teachers are in the database by the time we are adding a new course). This would be easy to do if this was a one-to-many relationship, but with a many-to-many relationship, we need to save the course before we can associate it with teachers.
My initial plan was to override Rails create method in the Course model to allow passing teacher_ids and validate presence of at least one teacher_id before saving the course, but I'm not sure this is a nice approach.
You should write custom validation, which is quite easy (please adapt to your code):
class Course < ActiveRecord::Base
has_and_belongs_to_many :teachers
validate :has_one_teacher_at_least
def has_one_teacher_at_least
if teachers.empty?
errors.add(:teachers, "need one teacher at least")
end
end
end
That way, you'll only be able to create courses if associated to one teacher like so:
teacher = Teacher.create()
course = Course.new()
course.teachers << teacher
course.save!

How to retrieve information that is a join of tables in rails?

My rails app is now working with ActiveRecord and I am really amazed about ruby on rails.
My DB has three tables:
Students(Id, name, parentId)
Parents(Id, name)
Grades(Id, studentId, value)
And the three models.
I have three views, in one of them I have to list the students, so I make a Student.all and it works.
But in one of the views I need to list the student with the parent's name and in the third one I nbeed the student's name and their grades.
I am completely lost now: if my model is my table, how can I retrieve this information which is a join of tables?
Thanks
Edit:
I added the line
Student.includes(:parent, :grades).all.each do |student|
And added the relations:
class Student < ActiveRecord::Base
belongs_to :parent
end
class Parent < ActiveRecord::Base
has_many :student
end
I had some problems when setting the relations, but now my page loads. Still I can't use the Student.parent.name property.
using <%= debug student %> I get the debug info, but no information about the parent. (students.parent shows the parent_id)
Any ideas what I'm missing?
It was a name conflict: my Students table had a column called parent. changing to parent_id solved it.
If you have your relations setup correct, you can do something like:
Student.all.each do |student|
puts student.name
puts student.parent.name
student.grades.each do |grade|
puts grade.value
end
end
You might also be interested in using includes in this case to avoid triggering a query for each student whose grades you retrieve. In this example you would replace:
Student.all.each do |student|
with
Student.includes(:parent, :grades).all.each do |student|
The difference is that in the first case Rails triggers a separate query to retrieve the grades for each student (and a query to get each parent's name). This is often called the "N+1 Problem," since if you have 10 students, to loop through each of them and get their parent's name would take 11 queries (one to get all the students, and one for each of the 10 students to get their parent's name).
When you use includes, Rails eager loads all the associated models and attributes in one query. For more information, check out:
http://guides.rubyonrails.org/active_record_querying.html

Count only datasets that are associated through HABTM

I have two models with a HABTM associaton called LegacyDatum and Category Now I want to count how many Objects of LegacyDatum are associated with one or more categories. When I call LegacyDatum.joins(:categories) that works but it also returns the datasets with no category. How can I only get the datasets that have at least one category associated?
The Model is really simple, no unconventional associations...
LegacyDatum:
class LegacyDatum < ActiveRecord::Base
has_and_belongs_to_many :categories
.....
....
...
end
I believe it's because all is loading everything before the joins method is called. What do you get if you drop the all portion, or tack it on the end instead?

Resources