Multiple relationships between two models in rails - ruby-on-rails

I have a comment system with two tables: comments, and users. On the comment I want to record who the author was and also I want to notify any user that is mentioned in the comment with (#username). So I'm thinking I need to have an author_id on the comment, and also a comments_users table with the comment id and all the users ids that were mentioned. Would this be a correct way to accomplish it?:
User:
has_many :comments
Comment:
belongs_to :users, class_name: 'User', foreign_key: 'author_id'
has_many :users

The associations could be set up thus:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :mentions, join_table: "comments_users", association_foriegn_key: "comment_id"
end
Class Comment < ActiveRecord::Base
belongs_to :author, class_name: "User", foreign_key: "author_id"
has_and_belongs_to_many :mentions, join_table: "comments_users", foreign_key: "comment_id"
end
#comments_users
comment_id | user_id
This will allow you to call:
#user.comments #-> shows comments user has authored
#user.mentions.first.comment #-> shows first comment user was mentioned in
#comment.author #-> shows user who authored comment
#comment.mentions.first.user #-> shows first user who was mentioned in comment
Update
HABTM still needs a table (Rails migration for has_and_belongs_to_many join table), but the difference is that it doesn't need a primary key column (just comment_id | user_id)
We've created a "self-referential" habtm relationship, meaning you don't need to "create" any records -- they should all be created already. The HABTM will just reference them. As such, you'll need to use the << ActiveRecord method to add records into your mentions collection:
#app/controllers/comments_controller.rb
Class CommentsController < ActiveRecord::Base
def create
#comment = Comment.new(comments_params)
#comment.save_with_mentions
end
end
#app/models/comment.rb
Class Comment < ActiveRecord::Base
def save_with_mentions
comment = self.save
#do something to find mentioned users
mentioned_users = User.where("user_id = 1") #example
for user in mentioned_users do
comment.mentions << user
end
end
end

There are always many ways to accomplish any given task, but I'm guessing you're looking for something like this for your models & associations.
User:
has_many :comments
The user model association looked right.
Comment:
belongs_to :author, class_name: 'User', foreign_key: 'user_id'
has_many :users
Note, the belongs_to should reference a model in singular-naming style (ie: user vs users). I think you're going to want to do a reference like comment.author to find the author of your comments. It is more typical to provide a foreign_key of user_id when referring to a User model to keep things clear, but then provide a clarifying association name like "author" or "creator" or whatever for reference as I showed above. So your Comments table would have a foreign_key of user_id to reference back to the Users table. This user would be referenced in Rails by the name "author".
The second part of your question that has to do with tracking other user references in your model sounds like a one-to-many from the comment-users table. So, that sounds like one option. Similar to your "author" comment, you may want to provide a clearer name like "tags" which can just be references to users.
Another good option for this feature may be to set up a polymorphic table (essentially a flexible join table) if you plan to use this principle elsewhere in your app (like for referencing/tagging people in other elements like a photo or posting or something). It could provide greater flexibility for adding features and tracking these user references. A polymorphic table could have any name, but usually has an "-able" type name - like "taggable". Here's a useful reference: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Related

Question about table/models logic before database generation

Im trying to make my first side project using rails to learn would you kindly help me understand this?
The basic idea is to have a betting game where one user generates a new bet that can only be accepted by another user (only 2 competitors assigned for each bet, the creator and the other player).
I'm thinking about 2 tables:
users
bets
Normally I would just have a one to many relationship for the user that created the bet. But I'm confused about the 'competitor' column where another user is also a user with a user_id. How can I express this relationship better than this:  
After thinking it through it doesn't look like a good setup because I'm renaming a column where I'm storing the user_id and having a many to many 'through' model doesn't make sense since it is a "only one competitor can participate in that bet".
I was thinking about a 'one to one through' creating a 'competitors' table like so:
Could you explain to me how to build it in a better way?
Many thanks!
just an idea, you can do this with 2 foreign_keys
so user can be as creator or competitors, you can also makesure that creator_id and competitor_id cannot be same value since user cannot bet with self
class Bet < ActiveRecord::Base
belongs_to :creator, foreign_key: "creator_id", class_name: "User"
belongs_to :competitor, foreign_key: "competitor_id", class_name: "User"
end
class User < ActiveRecord::Base
# as creator to create bet
has_many: creator_bets, foreign_key: :creator_id, class_name: "Bet"
# as competitor to create bet
has_many: competitor_bets, foreign_key: :competitor_id, class_name: "Bet"
end
#user = User.first
#user.creator_bets.build(...)
# this to create bet as creator
#user.competitor_bets.build(...)
# this to create bet as competitor
having a many to many 'through' model doesn't make sense since it is a
"only one competitor can participate in that bet".
Actually it does. Its is in many ways simpler than having muliple assocations pointing to the same table as you don't have to deal with the situation where a user could be in either column which requires something like:
# this gets much messier as you have to deal with more complex problems
Bet.where('bets.user_id = :id OR bets.competitor_id = :id', id: params[:user_id])
A many to many association also gives you the option of removing that limitation later with minimal redesign.
Given the following associations:
# This represents an event that users can bet on
# for example Elon Musk being the first man on mars.
class Event < ApplicationRecord
has_many :bets
has_many :users, through: :bets
end
class User < ApplicationRecord
has_many :bets
has_many :events, through: :bets
end
# This is the "join model" that joins User and Event
# columns:
# - user_id [bigint, fk]
# - event_id [bigint, fk]
# - amount [decimal]
class Bet < ApplicationRecord
belongs_to :user
belongs_to :event
end
You can simply create a bet by:
#event = Event.create!(description: "Elon Musk will be the first man on mars.")
#event.bets.new(user: User.first, amount: 500)
You can of course cap the number of users to two by adding a custom validation or in your controllers. If you only have two you can assume that event.users.first is the creator and event.users.last is the competitor.

Get association from custom name

My Seminar model has a column named teacher_id. That part of the relationship is working. I can call seminar.teacher to retrieve the User who teaches that seminar.
I'd like to be able to invert that query. In other words, I need to call teacher.own_seminars to get a collection of all the seminars where that User is listed as the teacher_id. I know that I could call Seminar.where(:teacher => teacher), but that's clunky. And I think that the performance is probably worse that way.
Note: Some of the Users are students who are linked to Seminar through the seminar_user join table, but I don't think that affects this question.
This is the model setup that isn't quite working yet
class User < ApplicationRecord
has_many :own_seminars, :class_name => "Seminar", foreign_key: 'own_seminar_ids'
end
class Seminar < ApplicationRecord
belongs_to :teacher, class_name: "User", foreign_key: 'teacher_id'
end
Cheers!
In foreign_key option, you specify the column which is, well, the foreign key.
The way has_many works, is it tries to guess, which one of the fields in the referenced entity corresponds to the primary key of this entity. By default, it's user_id (derived from name User). But since your column is actually called teacher_id, you should use that instead.
has_many :own_seminars,
class_name: "Seminar",
foreign_key: 'teacher_id'

rails join table issue with different roles (owner, non-owner)

In my app users can create products so at the moment User has_many :products and Product belongs_to :user. Now I want the product creator product.user to be able to invite other users to join the product, but I wanna keep the creator the only one who can edit the product.
One of the setups I've got in my mind is this, but I guess it wouldn't work, since I don't know how to distinguish between created and "joined-by-invitation" products when calling user.products.
User
has_many :products, through: :product_membership
has_many :product_memberships
has_many :products # this is the line I currently have but think it wouldn't
# work with the new setup
Product
has_many :users, through: :product_membership
has_many :product_memberships
belongs_to :user # I also have this currently but I'd keep the user_id on the product
# table so I could call product.user and get the creator.
ProductUsers
belongs_to :user
belongs_to :product
Invitation
belongs_to :product
belongs_to :sender, class: "User"
belongs_to :recipient, class: "User"
To work around this issue I can think of 2 solutions:
Getting rid of the User has_many :products line that I currently have and simply adding an instance method to the user model:
def owned_products
Product.where("user_id = ?", self.id)
end
My problem with this that I guess it doesn't follow the convention.
Getting rid of the User has_many :products line that I currently have and adding a boolean column to the 'ProductUsers' called is_owner?. I haven't tried this before so I'm not sure how this would work out.
What is the best solution to solve this issue? If none of these then pls let me know what you recommend. I don't wanna run into some issues later on because of my db schema is screwed up.
You could add an admin or creator attribute to the ProductUsers table, and set it to false by default, and set it to true for the creator.
EDIT: this is what you called is_owner?
This seems to be a fairly good solution to me, and would easily allow you to find the creator.
product.product_memberships.where(is_owner?: true)
should give you the creator

What models/associations do I need to use to implement this feature efficiently

I'm wondering if you can help me determine the best associations and maybe new models to use for a feature I'm trying to add to an existing app. The app lets users ask questions that are answered by other users. When posting a question, a user indicates which Province it belongs to (because the questions are location specific) and the user also selects one or more categories for the question. Thus, after the questions created, I'm able to get category (choosing one category) and province information like this
def show
#question = Question.find(params[:id])
puts #question.categories.first.id #id = 2 for Tourism, for example
puts #question.province_id #id = 6 for Ontario, for example
end
What I'd like to do is let some users (who answer questions) become featured users whose profiles will be displayed down the side of the views/question/show page. So in the question show action, according to the above example, I'd to query for the featured users for Tourism category in the province of Ontario.
Considering that there will hopefully be thousands of users of the app, but only a small pool of featured users, I don't think it's best to store that information on the user model, but rather maybe create a featured_user model that connects to their main user profile, a province (for which they are an expert) and the category (for which they are an expert), but they'd only be pulled up when they match both 'province and category' at the same time, so a featured_user whose category is "Tourism" and province is "Ontario" will only be pulled up if the question is in both category "Tourism" and province "Ontario."
If I create a featured_user model, what columns and associations should I create with these other models to make it work efficiently, and/or what should I add to existing models? I'm not sure if I should be trying to use some has_and_belongs_to_many or has_many :through or simple foreign keys. My instinct is to make it more complicated than it probably should be.
Right now
Province.rb
has_many :questions
Question.rb
belongs_to :province
has_many :categorizations
has_many :categories, through: :categorizations
Category.rb
has_many :categorizations
has_many :questions, through: :categorizations
Categorization
belongs_to :question, touch: true
belongs_to :category, touch: true
User.rb
has_many :questions #i.e. questions they've asked
has_many :answers #i.e. questions they've answered
I think the featured user model is the best solution. This encapsulate all featured user logic in one place and you can implement it without adding attribute columns to other models that (as you say) only would be used for a very small number of users. Here is how i would implement this:
# CreateFeaturedUsers (migration)
create_table :featured_users do |t|
t.references :user
t.references :province
t.references :category
end
# FeaturedUser (model)
belongs_to :user
belongs_to :province
belongs_to :category
# User (model)
has_many :featured_users
# Question (model)
def featured_users
User.joins(:featured_users).where(featured_users: {province: this.province, category: this.category})
end
However i would properly name it something else then FeaturedUser as the model doesn't represent a user but only the relation to a user. Maybe Featuring would be a better name.

ActiveRecords [something] belongs_to [User] and has_many [Users]

I'm modelling a scenario with Users and Tools, where a Tool is owned by one User but can be used by many Users including to one owning it.
I was thinking about adding an owner_id column to Tools and say it has_many Users or by adding a new relationsship table.
I'm really new to Rails and I have problems setting up the right associations in the models though, maybe you can point me in the right direction?
Thank you very much.
Your should add owner_id to the Tools table.
Associations will be like that.
class User < ActiveRecord::Base
has_and_belongs_to_many :tools
end
class Tool < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :owner, :class_name => 'User'
end
You'll need a tools_users table in order to use habtm-association. Generate a migration and create a table with option id: false and two columns user_id and tool_id:
class CreateToolsUsersTable < ActiveRecordMigration
def change
create_table :tools_users, id: false do |t|
t.integer :tool_id
t.integer :user_id
end
end
end
After that you can call something like #user.tools or #user.owner
Read more there
User has many tools
Tool belongs to user in owner
Tool has many users
is what I would do.
I'm not sure about the wording because I don't use Active Record but this is how it works in other orms

Resources