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!
Related
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 %>
I'm trying to build a student portal in Rails 3, but I'm having some problem.
The idea is to have a users table that contains all basic data for a given person. See the UML/E-R below for example attributes.
A user can be both an Assistant and a Student at the same time.
Assistant and Student should inherit from User.
The idea was to inherit directly from the User, like this.
class User < ActiveRecord::Base
# ...
def awesome?
[true, false].sample
end
# ...
end
class Student < User
has_one :student
has_many :registered_courses, through: :students
end
Student.new.awesome?
This makes the relations in the student model very strange.
has_many :registered_courses, through: :students
I want to be able to do something like this in the end.
student.full_name
student.pin_code
student.registered_courses
One solution would be to implementing the method by hand, like this
class Student < User
has_one :student
def pin_number
student.pin_number
end
end
But it looks really strange to refer to a student object inside the student model.
Is there a clearer, better way of doing this?
Here is an example UML/E-R. I've tried to keep this example clean by removing non relevant attributes. That is why there are so few attributes in the registered course entity.
STI is not a good choice for this the way that you have articulated it here, since users can be both students and assistants. When you are using STI, you generally add a type column to specify which subclass the record really belongs to. If both Student and Assistant inherit from User, then that really isn't an option, since you'd be forced to create duplicate User records for someone who is both an Assistant and a Student.
I think you'd be better off simply having Student and Assistant rows that belong_to a Student, and then delegating the elements that are contained in User back to the User object.
I feel like Inheritance is a bad move here. If you're going to have STI like this it HAS to be one or the other.
Instead throw all your logic into the User model, all your data is there anyway. Plus since Student & Assistant aren't mutually exclusive there shouldn't be any methods that will override each other.
Why not STI?
STI is mainly meant for objects that contain the same data, but does different things with them.
For example, I have a specification that contains multiple processes(ex. build and test). So I have a order that contains processes.
process_1:
order_id: 1
specification: foo
type: build
process_2:
order_id: 1
specification: foo
type: test
In this example the only thing that changes in the data is the type, but because the type changes I know what process to perform from the specification.
I'm trying to set up a proper database-design, but I'm stuck.
Here is what I'm trying to save.
Every user can define a vote history list from imdb looking like this.
Two users can define the same list.
First I want to be able to save each list as an imdb_vote_history_list - list.
class ImdbVoteHistoryList < ActiveRecord::Base
has_and_belongs_to_many :vote_history_list
has_and_belongs_to_many :movies
# Fields
# id (Integer) - defined by the user
end
Each list should be unique and is being defined by it's ID (given in the link).
Each list has and belongs to many movies, as in the code above.
Each user should be able to pick a name for every list.
So instead of saying
Each imdb_vote_history_list belongs_to user
I create a new relation called vote_history_list.
class VoteHistoryList < ActiveRecord::Base
has_and_belongs_to_many :imdb_vote_history_lists
belongs_to :user
# Fields
# name (String)
end
Here the user can pick any name for the list, without interference with other user's names.
Is this a good way to store the data?
From the theoretical database design view this is the right approach.
For example the entity relationship model describes it this way. You can have relationships between entities and attributes at those relationships. If you map those to a relational model (database tables) you get a table for the relationship containing references to both entites and all additional information.
This is what theory can tell us about it :)
My application has a user model with some simple access level checks. This access level determines the scope of access to the other models in the database. To be precise, I have District, School, Teacher, Room and Student models. An Admin can see all records, a District can see all child schools, teachers, rooms and students, a principal can see all child teachers, rooms and students, a teacher can see all child rooms and students.
This is done by associating a User object with one or more levels of models.
belongs_to :district
belongs_to :school
belongs_to :teacher
So a school principal would have a district id and a school id, but its teacher id would be null.
Access to the children is controlled via functions like this:
def teachers
if is_admin?
Teacher.all
elsif is_district_head?
district.teachers
elsif is_principal?
school.teachers
else
[ teacher ]
end
end
This func is treated in code as if it were a plain old has_many relationship, where we can do stuff like:
current_user.teachers.find param[:teacher_id]
current_user.teachers.each {|t| puts t.id }
Whether the current_user is an admin or a teacher or anything in between, the correct amount of teachers is returned.
Except, that's sadly not the case. Only the actual has_many relationships work fully, my fake ones fail when I try to use .find or some function that's specific to the ActiveRecord collections created by has_many.
So, on to my question. How can I return an ActiveRecord collection object without explicitly calling the has_many function?
If you think I'm a bleedin' retard and I'm missing something godawfully obvious, please don't hesitate to enlighten me! I had thought this system was going great, until I had to use a .find off an administrator level user account. It was essentially running
Teacher.all.find :conditions => 'xyz'
...and that sadly returns an Enumerator object instead of an ActiveRecord for the Teacher model.
Basically you have four roles (Admin, District Head, Principal, and Teacher(?)). Each user has one of these roles, and you have a set of rules to determine the access privileges for each role. Correct?
This problem has been solved. I would urge you to consider an authorization system such as CanCan or Declarative Authorization. Both contain straightforward ways to handle the issues you are struggling with here. There's a learning curve, or course, but the time will be well spent.
Is there a way to validate the uniqueness of an attribute among columns in two different models. For example:
I have a bike model and a car model. When I create a new bike, I want to validate that the name of the bike is unique in that there is no other bike or car with that name. I don't want to put these into one model because they have vastly different properties. I'm on rails 2.3.8
Thanks.
Rails doesn't validate across models (I don't think, anyways) automatically. You should probably just write your own method to check, a la…
class YourModel < ActiveRecord::Base
validates :uniqueness_of_a_property_across_models
def uniqueness_of_a_property_across_models
// check the other model
end
end
Maybe your Car and Bike Models can have somes common properties like this name, and they can both inherit a common model, and have your uniqueness validation on this model ?