Question about table/models logic before database generation - ruby-on-rails

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.

Related

Rails model associations - has_one or single table inheritance?

I'm having trouble deciding between Single Table Inheritance and a simple has_one relationship for my two models.
Background: I'm creating a betting website with a "Wager" model. Users may create a wager, at which point it is displayed to all users who may accept the wager if they choose. The wager model has an enum with three statuses: created, accepted, and finished.
Now, I want to add the feature of a "Favorite Wager". The point of this is to make it more convenient for users to create a wager, if they have ones they commonly create. One click instead of ten.
FavoriteWagers exist only as a saved blueprint. They are simply the details of a wager -- when the User wants to create a Wager, they may view FavoriteWagers and click "create", which will take all the fields of the FavoriteWager and create a Wager with them. So the difference is that FavoriteWagers acts as only as a storage for Wager, and also includes a name specified by the user.
I read up on STI, and it seems that a lot of examples have multiple subclassing - eg. Car, Motorcycle, Boat for a "Vehicle" class. Whereas I won't have multiple subclasses, just one (FavoriteWager < Wager). People have also said to defer STI until I can have more classes. I can't see myself subclassing the Wagers class again anytime soon, so that's why I'm hesitant to do STI.
On the other hand, has_one doesn't seem to capture the relationship correctly. Here is an example:
Class User < ApplicationRecord
has_many :favorite_wagers, dependent: :destroy
has_many :wagers, dependent: destroy
end
Class FavoriteWager < ApplicationRecord
has_one :wager
belongs_to: user, index: true, foreign_key: true
end
Class Wager < ApplicationRecord
belongs_to :favorite_wager, optional: true
belongs_to :user
end
I've also thought about just copying the fields directly, but that's not very DRY. Adding an enum with a "draft" option seems too little, because I might need to add more fields in the future (eg. time to auto-create), at which point it starts to evolve into something different. Thoughts on how to approach this?
Why not just do a join table like:
Class User < ApplicationRecord
has_many :favorite_wagers, dependent: :destroy
has_many :wagers, through: :favorite_wagers
end
Class FavoriteWager < ApplicationRecord
belongs_to :wager, index: true, foreign_key: true
belongs_to :user, index: true, foreign_key: true
end
Class Wager < ApplicationRecord
has_one :favorite_wager, dependent: destroy
has_one :user, through: :favorite_wager
end
Your FavoriteWager would have the following fields:
|user_id|wager_id|name|
That way you can access it like:
some_user.favorite_wagers
=> [#<FavoriteWager:0x00007f9adb0fa2f8...
some_user.favorite_wagers.first.name
=> 'some name'
some_user.wagers.first.amount
=> '$10'
some_user.wagers.first.favorite_wager.name
=> 'some name'
which returns an array of favorite wagers. If you only want to have ONE favorite wager per user you can tweak it to limit that. But this gives you the ability to have wagers and users tied together as favorites with a name attribute. I don't quite understand your use case of 'a live wager never has a favorite' but that doesn't matter, you can tweak this to suit your needs.

Rails field on join entity in has_many through relationship

I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.

Multiple relationships between two models in 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

How to associate existing model with new model in Rails

I have a 'tip' model and a 'team' model. I am trying to create a form where a user will select the winning teams from several games. So far the user can select the winning teams and those id's are being saved as foriegn keys in the Tip model (team_id). But after saving, I can't go (for example ) #tip.team.name . What do I need to do ? How do I associate it (I though rails might magically do it if foriegn key is set), I am very new to rails. Thanks for any help !
class Tip < ActiveRecord::Base
attr_accessible :user_id, :team_id, :team, :game_id, :game, :comp, :comp_id
belongs_to :game
belongs_to :comp
belongs_to :team
end
class Team < ActiveRecord::Base
attr_accessible :name
has_many :tips
end
def create
params['game'][0].each do |key, value|
#tip = Tip.new
#tip.team_id = value
#tip.game_id = key
#tip.save
end
This last method may be messy too, but not sure how else to do it. There are several 'tips' that I want to create in the one form.
EDIT : To be clear I am quite sure it's a one to many relationship, I am creating several tips in the one form but each tip only relates to one team.
EDIT : Actually my approach (which I'm sure is not close to the best way, did allow tip.team.name. It was a silly error relating to my test data that made me think otherwise.
What you really need is to use a has_many through relation to link the tips and the teams. This is because one tip can have many teams, but also one team can be on many tips. You will need to create a third table to do this, maybe named TeamsTips. Here is how you might set it up:
class Tip < ActiveRecord::Base
has_many :teams_tip
has_many :teams, :through => :teams_tip
end
class TeamsTip < ActiveRecord::Base
belongs_to: teams
belongs_to: tips
end
class Team < ActiveRecord::Base
has_many :teams_tip
has_many :tips, :through => :teams_tip
end
Now when you have a #tip, you can find all the teams for it with #tip.teams. Remember, this will be an array so to get the first team use #tip.teams[0]. Likewise, if you have an #team, you can get all the tips for it with #team.tips.
For more informations on how to setup this has_many through association, see A Guide to Active Record Associations
if ur asscociation is "Tip belongs to a Team " and "Team can have many Tips" then the association u defined in the question is correct.
if u want to created multiple tips when a team is created or add/edit/delete tips for a already created team, have a look at "accepts_nested_attributes_for" and https://github.com/ryanb/nested_form.
If u can get the team name using '#team.name' then u should get it using "#tip.team.name"

Linking 2nd row to different database table, in rails

lets say I have the Users table, and the Team table.
In Rails, I know how to link the user_id column in the Team table to the Users table. But what if I have a second column I also want to link to the user's table, such as user_id2 (this essentially creates an order in the team table)?
Is there a solution, or something I don't know about to do what I'm trying? I also don't think the "has_many" is what I'm looking for, because user_id might be the team manager, and user_id2 might be the team captain, i.e. they have different roles affiliated with them, and order is important.
Thanks!
Edit: for my purposes, I also wouldn't need more than these two user relations. (i.e. cases for three wont be relevant)
You may want to look into STI (look for Single Table Inheritance on that page) or Polymorphic Associations. Either would allow you to express your intent a bit more clearly, although there isn't enough information in your question for me to puzzle out which would fit best.
Give those a read and see whether they accomplish what you want.
First here is a way to do this in one direction (Team -> User), but it wouldn't work for the reverse direction, and there's a better option I'll get into afterwards. The first one assumes you have columns named manager_id and captain_id on the teams table.
class User < ActiveRecord::Base
end
class Team < ActiveRecord::Base
belongs_to :manager, :class_name => ::User
belongs_to :captain, :class_name => ::User
end
However, I'd be surprised if a Team only consisted of two Users (the manager and captain) - it's more likely that you'd want a join table to track all of the users' team memberships. That join table (called team_memberships in this example) could have a role column that holds the manager/captain info, as well as any other data you have. This way is a lot more flexible, and offers additional benefits, like being able to track historical team data if team members change over time, which they will.
class Team < ActiveRecord::Base
has_many :team_memberships
has_many :users, :through => :team_memberships
def captain
team_memberships.captain.first
end
def manager
team_memberships.manager.first
end
end
class TeamMembership < ActiveRecord::Base
belongs_to :user
belongs_to :team
# You'll need some database-level UNIQUE INDEXes here to make sure
# you don't get multiple captains / managers per team, and also
# some validations to help with error messages.
named_scope :captain, :conditions => {:role => "captain"}
named_scope :manager, :conditions => {:role => "manager"}
end
class User < ActiveRecord::Base
# depending on the rules about teams, maybe these should be has_many...
has_one :team_membership
has_one :team, :through => :team_memberships
end
Check out http://guides.rubyonrails.org/association_basics.html for more details.

Resources