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.
Related
It's been a while since I've worked with Rails (Rails 6) on a new application. I'm failing to recall how to properly set up associations. I have two problems that are rather similar and may help to answer each other. I understand that the model containing belongs_to will hold the foreign key, however, it feels backward in my head.
I have a User model with a pronouns attribute. I have a Model/Table pronouns. A user can have one pronouns record associated with is (has_one?). But a pronouns record can belong to any number of users. So belongs_to won't work in this case, since the foreign key would be on the pronouns table. The way that I've sort of gotten around this is to use belongs_to, but this doesn't feel right because I need to make it optional since pronouns isn't required for a user.
class User < ApplicationRecord
belongs_to :pronouns, optional: true
end
class Pronoun < ApplicationRecord; end
I have the following models: Location, User, Experience, JobPost. In this case, User, Experience, and JobPost can have a single location, but Location can belong_to (have_many?) of each of these. This feels like a case for a polymorphic association. Where I'm feeling confused is what the has_one model (user, experience, job post) looks like. Do they have one? They shouldn't have many.
This is a work in progress, so there isn't much more code-wise I can really add. But if there are any areas where I can share more or elaborate, I'm happy to.
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
I have a Record model and in order to edit this model, you must be logged in as an instance of Admin. I would like to have a column called last_modified_by which points to the Admin who last modified the Record. In the database, I was thinking it would be good in the records table to add a column that holds the Admin's id; however, the only way I know how to do that is with an association. These two models are not associated with each other so an association doesn't make a lot of sense. Is there any other way I might be able to accomplish this task without resorting to associations? Any advice would be much appreciated!
Hmm, I think the association is a good tool here. You might want to try to hack it somehow but I think nothing you can conjure up will ever be as good as an association via a foreign_key(also so fast). But perhaps you would like to name your association and do something like:
class Record < ActiveRecord::Base
belongs_to :culprit, :class_name => 'Admin', :foreign_key => 'last_modified_by'
end
or give it some more senseful naming?
You could create an Active Record before_save callback. The callback would save the admin's id into the last_modified_column. This would make sure the admin id is saved/updated each time there is a change to the model.
For example, assuming admin is #admin:
class Record < ActiveRecord::Base
before_save :save_last_modified
def save_last_modified
self.last_modified_column = #admin.id
end
As for getting #admin, you could employ a method similar to this, and set #admin = Admin.current (like User.current in the link) somewhere in the Record model.
I have a Tournament model that needs 0, 1, or 2 contacts. I created a Contact model and set has_many :contacts on the Tournament and belongs_to :tournament on the Contact. The Tournament accepts_nested_attributes_for :contacts.
However, when I build the form for Tournament I don't quite see how I should do it. I'm thinking about having two fields_for :contacts but it feels messy. I also considered having two specific attributes on the Tournament model (something along the line of primary_contact and secondary_contact) but I'm not sure about how to do that.
Is there a "correct" way to do this? Any suggestions?
I'm on Rails 3.1 BTW.
fields_for :contacts is the right way to go.
Take advantage of the fact that, if tournament.contacts has multiple items, then a single fields_for :contacts will show multiple fieldsets.
Then take advantage of the fact that tournament.contacts.build will create an unsaved Contact and add it to the contacts collection. If you do this in the controller before showing the form then your fields_for will display this empty contact and use it correctly with its nested attributes
I think you shouldn't limit the contacts for 2 fields, because I think you should keep the flexibility of adding more contacts for a tournament later
I have done a small example (by using check boxes) between Project to users, you might be able to get idea
https://github.com/sameera207/HABTMsample
I'd suggest maybe adding a non-persistent contact_list attribute and then you could enter as many contacts as you need separated by commas into one field:
has_many :contacts
attr_accessor :contact_list
def contact_list=value
value.split(',').each do |email|
self.contacts.build(:email => email).save
end
end
def contact_list
self.contacts.join(',')
end
If you need to enter more information for each contact (not just a name, email, or phone number), then you would need more fields.
The following railscast may help you:
http://railscasts.com/episodes/196-nested-model-form-part-1
I have three tables/models. User, Alliance and Alliance_Membership. The latter is a join table describing the :Alliance has_many :Users through :Alliance_Membership relationship. (:user has one :alliance)
Everything works ok, but Alliance_Membership now has an extra field called 'rank'. I was thinking of the best way to access this little piece of information (the rank).
It seems that when i do "alliance.users", where alliance is the user's current alliance object, i get all the users information, but i do not get the rank as well. I only get the attributes of the user model. Now, i can create a helper or function like getUserRole to do this for me based on the user, but i feel that there is a better way that better works with the Active Record associations. Is there really a better way ?
Thanx for reading :)
Your associations are all wrong - they shouldn't have capital letters. These are the rules, as seen in my other answer where i told you how to set this up yesterday :)
Class names: Always camelcase like AllianceMembership (NOT Alliance_Membership!)
table names, variable names, methods and associations: always underscored and lower case:
has_many :users, :through => :alliance_memberships
To find the rank for a given user of a given alliance (held in #alliance and #user), do
#membership = #alliance.alliance_memberships.find_by_user_id(#user.id)
You could indeed wrap this in a method of alliance:
def rank_for_user(user)
self.alliance_memberships.find_by_user_id(user.id).rank
end