Messaging Associations in Rails - ruby-on-rails

I'm creating a Rails messaging application (using Twilio, which isn't super relevant, I suppose) which has the following models:
Contacts
Phone Numbers
Messages
Contacts has many Phone Numbers, easy enough.
Messages has many phone numbers, except it's not many, it's exactly two. A from and a to.
I'd like to be able to make calls such as:
#message.from.contact
Here are some approaches I've thought about:
I found the answer to this prior question. If this is the way to do, I'm not certain what the right migration would be to get the messages table to have the necessary columns. Do I need to migrate by both adding a column and a foreign key? Tried this and got stuck.
Another approach I thought of was to create a join table with a string attribute containing either "from" or "two." I assume then then I'd need to create my own Message.from method that searches for message_phone_numbers with "from" in some string? That feels unwieldy.
Or a third possible way... a FromPhoneNumber class and ToPhoneNumber class that has the join info.
I know I'm not posting any code. I've done some migrating and rolling back and am back to square one now...
Thanks for any help. I'm new to Stack Overflow and new to Rails, so your assistance will mean a lot!

Yes, your first bullet point is the correct answer. For your Message model have attributes from_id and to_id which both reference a particular phone number. I have inferred the names of your models so you may need to correct those but in summary:
Migration should look like:
add_column :messages, :from_id, :integer
add_column :messages, :to_id, :integer
Message class should look like:
belongs_to :from_phone_number, class_name: 'PhoneNumber'
belongs_to :to_phone_number, class_name: 'PhoneNumber'
PhoneNumber class should look like:
has_many :sent_messages, class_name: 'Message', foreign_key: 'from_id'
has_many :received_messages, class_name: 'Message', foreign_key: 'to_id'
You could stub out some methods in your message class to give you the short hand methods you are looking for:
def from
from_phone_number
end
In my view, just having from as an attribute on the model itself is not very descriptive and could mean the user or phone number.

Related

Rails association has_many and has_many through on same STI model

I have a model Note.
Notes are very useful and are used for lots of purposes throughout the application.
We have another model User, and these are STI split between
User1s
and
User2s
We have a third model Thing, which are things belonging to Users... I'll leave it there for the moment as it gets a touch more complicated, (there are other models a bit like Things which one can likewise make notes on).
Note.rb
belongs_to :user #This signifies that the user wrote the note
belongs_to :notable, polymorphic: :true #This is what the note is referring to
User.rb
has_many :user_notes, class_name: "Note", foreign_key: "user_id"
#signifies the note was written by the User
User1.rb < User
has_many :notes, as: :notable
#Signifies the note was about the User1
has_many :things
User2.rb < User
has_many :notes, as: :notable
#Signifies the note was about the User2
Thing.rb
belongs_to :user1
has_many :notes, as: :notable
#The note is about the thing
These can be, but are not generally, self-referential (I write a note about myself).
Often User1s will write notes about User2s, or about User2s Things so that User2 can see them.
I think I've taken the correct route in renaming the user_notes.
I'm struggling with how one collects together all the notes that might be related to me.
i.e. Let's say I'm a user1.
I'd like to see the notes I've written, plus notes other people have written about me, plus the notes that have been written about my things.
Is there an easy way to do that?
I'm struggling with two points.
How can I connect the notes about my things?
has_many :thing_notes, through: :things, source: :notes
works, but I wonder if it's the correct way of approaching it.
Also, I'm interested in the reverse (which is also sort of useful), in this case ignoring who might have written the note, to return the relevant user1, if there is one. But...
delegate :user1, to: :notable
doesn't make sense if user1 is already a potential notable_type.
How can I collate all my notes (of whatever type) and return them?
i.e.
#user1.notes
will return just the notes about me
#user1.user_notes
will return notes I've written
#user1.thing_notes
will return notes about my things
I could merge them together, but I'll risk there being annoying duplicates, and it seems a shame to make multiple database calls and lose db ordering when they're all of one type...
Is there an obvious way to get all my notes...?
Note.where("(user_id = :user1_id) OR (notable_id = :user1_id AND notable_type = 'User') OR (notable_type = 'Thing' AND notable_id IN (:things))", user1_id: user1.id, things:user1.thing_ids)
is a touch on the ugly side and will only get uglier...
Hope that makes sense to someone who loves to solve such puzzles...
For the collation, if you're using Rails 5, you could do something like this to simplify the raw SQL:
Note.where(user: user1).or(Note.where(notable: user1)).or(Note.where(notable: user1.things))
If you're not using Rails 5, you can include this gem for the same functionality: https://github.com/Eric-Guo/where-or

How to 'hide-users' in application

So I have an application with users/user-profiles. Presently, users can view others profiles, send messages, and favorite user profiles. I'd like to also add the feature to allow users to 'hide-user' profiles so that they won't see the user ever again in search, or anything. And provide the option in 'settings' to 'un-hide' the user as well.
How might I do this? I haven't a clue as to where to begin with this feature.
(If you need any code examples, models, controllers please ask and I will happily provide)
Kind regards,
Sonny
There are probably a couple of ways to do this, the first approach that comes to mind would be to establish a self-referencing many to many relationship.
You will need to create the join table (I shall call it suppressed_users). I will show the rails model, as the migration wouldn't have anything other than the foreign keys.
class SuppressedUser < ActiveRecord::Base
belongs_to :user
belongs_to :suppressed_user, :class_name => "User", :foreign_key=>"suppressed_user_id"
end
And in the User model, in order to help DRY up your code, you can use a scope to easily filter all the users that this target user has decided to suppress (or hide):
class User < ActiveRecord::Base
has_many :suppressed_users // Optional
scope :without_hidden_users, -> (target_user) do
where.not("exists (?)",
SuppressedUser.select("1")
.where("users.id = suppressed_users.suppressed_user_id AND suppressed_users.user_id = ?", target_user))
end
end
Note about the scope: What I'm doing here is creating a dependent (or correlated) subquery, in which I check whether the target user has suppressed (or hidden) the user we're looking at at the moment. In other words, the dependent subquery is executed for each row in the users result set (those not filtered by other where or join conditions and such). With proper indexing, this should not have an impact on performance.
Note about has_many :suppressed_users: This is technically not needed for the query I've shown, so if it is not relevant for anything in your system, you should be safe to remove it.
So, if I am presently logged in, and I want to search for a list of users meeting some condition, in your controller you would do something like this:
User.without_hidden_users(#current_user.id)...other conditions and such as needed
Assuming #current_user represents the currently logged in user.
I believe one approach would be to create a many_to_many relationship via the has_many :through association between users and hidden users. And likewise another many_to_many relationship between users and hiders (i.e. users who are hiding the user). In the end, you should be able to do something like this:
some_user.hidden_users and some_user.hiders (i.e. all the users that are hiding some user). You probably won't need the second one most of the time.
So the first thing to do would be to create a join table called hiders_hidden_users (or whatever you want) which would contain only two fields: hider_id and hidden_user_id.
Note, that in reality, those ids will both refer to a User record. Your HidersHiddenUser model will look something like this:
class HidersHiddenUsers < ActiveRecord::Base
belongs_to :hidden_user, class_name: "User", foreign_key: "hidden_user_id"
belongs_to :hider, class_name: "User", foreign_key: "hider_id"
end
Then, you need to set up the relationships in the User class:
class User < ActiveRecord::Base
has_many :hiders_join, class_name: "HidersHiddenUser", foreign_key: "hider_id"
has_many :hidden_users_join, class_name: "HidersHiddenUser", foreign_key: "hidden_user_id"
has_many :hiders, through: :hiders_join
has_many :hidden_users, through: :hidden_users_join
end
Note, that you have to specify the class name and foreign key when writing the has_many relationship with the join table. Note, also that you have to specify the relationship twice with the same model.
Then, say some_user wants to hide user1 and user2. All you would need to do is something like this:
some_user.hidden_users << user1
some_user.hidden_users << user2
Although I have not tested this, I believe it should work.

Implementing a model with two separate attributes with different names but with same type

I have a Model named Lock which keeps track of two Students who have locked a deal with each other. My Lock model has attributes buyer_id and seller_id which both are the id's of users from my User model. Is there a way to have attributes buyer and seller (for Lock model) that directly access their appropriate User?
For example, I would like to make a call like Lock.last.buyer.email instead of having to go through the user_id like User.find(Lock.last.buyer_id).email.
Also, will this difference in syntax make for better code? Or am I merely making a syntactical improvement for my own readability? Either way, I would like to know if its possible :)
Try these
belongs_to :buyer, class_name: "User", foreign_key: :buyer_id
belongs_to :seller, class_name: "User", foreign_key: :seller_id
There is more info about belongs_to in the documentation.

Ruby on Rails model associations

New in rails here. I have trouble understanding this specific activerecord association. Can someone help me on this. The model looks like this:
class User < ActiveRecord::Base
has_many :client_occurrences,
foreign_key: "client_id",
class_name: "Occurrence"
has_many :requested_occurrences,
foreign_key: "requestor_id",
class_name: "Occurrence"
end
And the one it's associated to is:
class Occurrence < ActiveRecord::Base
belongs_to :template, autosave: true
belongs_to :requestor, class_name: "User"
belongs_to :client, class_name: "User"
end
I just can't seem to understand the associations being portrayed here. Everytime I see the user model, I immediately classify it as an issue because here's how I read the association in the user model:
User has many occurrences alias by client_occurrences and set
client_id as foreign_key
It's an issue for me since the foreign_key is not in the proper table (According to my understanding of the code). In addition, client_id and requestor_id are columns found in the Occurrence table.
Could anyone help?
I'm not sure where your issues are. I would say your reading is correct, namely:
User does have many Occurences (each Occurence points back to
the User)
They are aliased/referenced as client_occurrences from
the perspective of the User The foreign_key is indeed
client_id.
That is, the Occurence table uses client_id to point
to the User
From the point of view of Occurrence:
Each Occurrence belongs to a :client, which means the field name will be client_id (which matches the foreign_key clause in the User model)
The item being pointed to is really a User
One of the things that's confusing, I think, is that the order of the has_many clauses is different from the order of the corresponding belongs_to clauses.
These are the business rules I gather from that:
A User can be associated with an Occurrence as a client
A User can be associated with an Occurrence as a requestor
A User can be associated to many Occurrences
An Occurrence has one requestor User, and one client User
The foreign key is specified in the User model because it's associated to the same model multiple times, otherwise rails would default to using "user_id" as the foreign key in the Occurrence model.
Check this link out for the full details on what all the different ActiveRecord Associations do:
Rails Guides: ActiveRecord Associations

Should I use `has_and_belongs_to_many` or `has_many :through` in my model?

I have read the Choosing Between has_many :through and has_and_belongs_to_many on the Rails website, however I am a bit confused since I have a different case to the ones given on the website.
I have two models: Prop and CharacterCostume, and the character's costume can have multiple props associated to it, but a prop doesn't belong to that character and it can be used by any number of characters in the scene, too.
Right now I have has_and_belongs_to_many :props inside my CharacterCostume model, which does exactly what I want it to do: it fetches all the props associated with the costume using a table named character_costumes_props when I call CharacterCostume#props
However the association name is putting me off because of the "belongs to many" part. The costume does not belong to any of the props, so there's no has_and_belongs_to_many :character_costumes inside the Prop model.
I know that it can all function fine without it, but it got me thinking that maybe I should use a has_many :through association, but that requires me to create a superfluous model (it is superfluous, right?) and the model would look like this:
class CharacterCostumeProp < ActiveRecord::Base
belongs_to :character_costume
has_one :prop
end
Also, would has_one instead of belongs_to work here?
I want the code to be as semantic as possible, but I am not sure if this will increase the requirement for resources or decrease performance in some way, since there's an intermediate model.
Are there certain quirks/benefits attached to either approach? Is mine good enough? Or is my thinking completely wrong from what I need to do?
Thanks!
I think you want to use a :has_many, :through because you're going to want to work directly with the relation model - what scene(s), consumed or damaged, etc.
But, the reason it reads funny to you is that, for the most part, has_many and belongs_to don't really mean what they mean in English. What they really mean is "They have the foreign keys" and "I have the foreign key", respectively; the exception being the :dependent => :destroy behavior.
That still doesn't really help with has_and_belongs_to_many, since you're then saying, "They have the foreign keys and I have the foreign keys` - except that you can think of it sort of adding a new part both to "I" and "They" that happens to be the same part for each, and has those keys.
Does that help?
The single most important question you must ask yourself when deciding between HABTM and has_many :through is this:
Do I want to store any information specific to the association?
Example 1: magazine subscriptions
A many-to-many relationship between readers and magazines might conceivably be structured as a HABTM or a has_many :through. However, the latter makes far more sense in this case because we can easily think of information specific to the association that we might want to store.
A reader is related to a magazine through a subscription, and every subscription can be described by fields such as price, starting date, issue frequency and whether it's active or not.
Example 2: tags
The relationship between an existing Tag model and, say, an Article model is clearly of the many-to-many kind. The fact that one particular tag has been associated to any particular article must have no influence on whether the same tag will be able to be similarly associated to other articles in the future.
But, differently from the previous example, here the association itself is all the information we need. We just need to know which tags are associated to any given article. It doesn't matter when the association was formed. It doesn't matter how long it lasted.
It may matter to us how many articles a tag is associated with. But that information is stored in the Tag model since it's not specific to an association. There is even a Rails feature that takes care of that called counter_cache.
has_one wouldn't work, you'd need belongs_to
it is not superfluous if you have logic in your association model
has_and_belongs_to_many is good enough for you
See example below
class Student
has_and_belongs_to_many :courses
has_many :teachers, through: :courses
end
class Teacher
has_many :courses
has_many :students, through: :courses
end
class Course
has_and_belongs_to_many :students
belongs_to :teacher
def boring?
teacher.name == 'Boris Boring'
end
end
In the example above, I make use of both versions. See how Course would have its own logic? See how a class for CourseStudent might not? That's what it all comes down to. Well, to me it is. I use has_many through for as long as I can't give a proper name to my association model and/or the model doesn't need extra logic or behavior.

Resources