I'm trying setup a Rails app that will be something like a game. The app has Users, each of which have Pawns that they can create. A User can search other users and the Pawns that they created, and challenge another one if they like, using one of their own Pawns. The challenged user can then accept/decline the challenge.
Right now I can add/delete Pawns for a User fine, and my models look like this:
class User < ActiveRecord::Base
has_many :pawns, dependent: :destroy
and
class Pawn < ActiveRecord::Base
belongs_to :user
Now, if User1 wants to challenge a Pawn created by User2, he looks at User2's list of Pawns and clicks a "Challenge" button for the Pawn he wants. User1 then has to select one of his Pawns to use for the challenge and clicks save. Now User2 needs to either accept/decline the challenge.
I'm having a hard time wrapping my head around how the challenges should be setup. My thought is that each Pawn will have a self-referential many-to-many relationship, almost like a friendship relationship would be setup. However, I don't know if I should consider the challenge something related to the User or the Pawn.
Whats the best way to model something like this?
EDIT:
Here's a diagram of what I'm trying to accomplish. I definitley think I need some sort of association setup. Result would hold statistics of that Pawn for that Challenge (something like time_spent, clicks_made, etc.). Challenge would also have a column for winner or something similar.
Your challenge may have association defined for each type of pawn.
class Challenge < ActiveRecord::Base
# attributes :challengee_id, :challenger_id
belongs_to :challengee, class_name: "Pawn"
belongs_to :challenger, class_name: "Pawn"
has_many :results
end
Pawns will have associations for each type of challenge.
class Pawn < ActiveRecord::Base
# attributes :user_id
belongs_to :user
has_many :results
has_many :initiated_challenges, class_name: "Challenge", foreign_key: :challenger_id
has_many :received_challenges, class_name: "Challenge", foreign_key: :challengee_id
end
It's probably ok to denormalize challenge_id for the result records.
class Result < ActiveRecord::Base
# attributes: pawn_id, challenge_id, :result
belongs_to :pawn
belongs_to :challenge
end
Your user can have associations to pawns and to challenges through pawns. A simple way to get both challenge types associated with a user would be to combine the results of the two challenge associations (initiated and received) into one method #challenge.
class User < ActiveRecord::Base
has_many :pawns
has_many :initiated_challenges, through: :pawns, source: :initiated_challenges
has_many :received_challenges, through: :pawns, source: :received_challenges
def challenges
initiated_challenges + received_challenges
end
end
Performance optimizations for this method could include denormalizing the user_ids on to Challenge as :challengee_user_id and :challenger_user_id... or caching a list of challenge ids on the user so you make one query instead of two.
You can set up another table called Challenges with fields challenger_id, challengee_id, status. The challenger and challengee ids would represent the pawns of course, not the user. The status would represent challenge_pending, challenge_on_going, there are other ways to do this obviously, but this one one.
This has the added benefit of allowing you to restrict pawn-to-pawn challenges to one each very easily if that's your desired behavior, among other things.
In your view controller
#challenges = Challenge.where("challengee_id IN (?)", Pawn.find_all_by_owner_id(current_user.id).map{|u| u[:id]})
#challenged = Challenge.where("challenger_id IN (?)", Pawn.find_all_by_owner_id(current_user.id).map{|u| u[:id]})
In your view
<%= #challenges.each do |challenge| %>
whatever
<% end %>
class User < ActiveRecord::Base
has_many :pawns
end
class Pawn < ActiveRecord::Base
belongs_to :user
has_many :challengers, class_name: 'Challenge'
has_many :challengees, class_name: 'Challenge'
belongs_to :result
end
class Challenge < ActiveRecord::Base
belongs_to :challenger, class_name: 'Pawn'
belongs_to :challengee, class_name: 'Pawn'
belongs_to :result
end
class Result < ActiveRecord::Base
has_one :challenge
has_one :winner, class_name: 'Pawn'
end
Related
I'm uncertain of the best approach to model conversation relationships.
My application has Users, Conversations, UserConversations, Venues, & VenueConversations.
Users can have many Conversations & Conversations can have many Users. Thus, the bridging table (UserConversations).
Venues can also have Conversations and these Conversations can also have many Users. Thus, the bridging table (VenueConversations)
I've tried it with polymorphic associations but then Messages in the Conversations became difficult to manage. Also, a while back I asked a question on Stack and someone said I should steer clear of polymorphic associations at all costs.
I hope to do this properly so that querying doesn't become a nightmare in the future.
I'd also like to state I'd like to have one Conversation model because one day they'll share a lot of behavior.
User.rb
class User < ApplicationRecord
has_many :user_conversations
has_many :conversations, through: :user_conversations
has_many :venue_conversations
has_many :conversations, through: :venue_conversations
end
UserConversation.rb
class UserConversation < ApplicationRecord
belongs_to :user
belongs_to :conversation
end
Venue.rb
class Venue < ApplicationRecord
has_many :venue_conversations
has_many :conversations, through: :venue_conversations
end
VenueConversations.rb
class VenueConversations < ApplicationRecord
belongs_to :user
belongs_to :venue
belongs_to :conversation
end
The problem I'm having now is that when I do User.first.conversations I only find one of the types of conversations a User could have(Private vs Venue). It reflects the last definition I gave to has_many :conversations in the User.rb model. Perhaps a scope is in order? How would I make a distinction between private & venue conversations as well?
Please advise me on how I could resolve this or take a better approach
I have Users and Trucks. I want the ability to say #truck.drivers << #users and #user.truck = #truck.
The solution is simple until I want the relationship to be stored in a join table.
# tables
:users
:id
:truck_drivers
:user_id
:truck_id
:truck
:id
I've gotten it to where I can say #truck.drivers << #user and #user.trucks << #truck, but I would like to limit a user to occupy one truck at a time, for my sanity.
Is it possible? A has_many/belongs_to with a join table? Or should I try a different approach? I'm not using a third model for the join table. It's just a table. Here's what I have so far.
class User < ApplicationRecord
has_and_belongs_to_many :trucks,
join_table: :truck_drivers, # points to the table
class_name: :Truck # looks for the truck model in the table
end
class Truck < ApplicationRecord
belongs_to :company
has_and_belongs_to_many :drivers,
join_table: :truck_drivers,
class_name: :User
end
The reason I need a join table in the first place is because each User can have many Roles: Admin, Manager, Driver, Customer Service, etc. I thought it didn't make sense to add a truck_id to all the users if all the users are not going to be using trucks.
It seems like you ought to be able to do something like:
#user.trucks << #truck unless #user.trucks.any?
Yes this is a standard strategy with rails using the :through keyword.
rails documentation: http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Make a model called TruckUser with truck_id and user_id
then edit your classes:
class User < ApplicationRecord
has_many :truck_users
has_many :trucks, through: :truck_users
end
class Truck < ApplicationRecord
belongs_to :company
has_many :truck_users
has_many :drivers, through: :truck_users
end
class TruckUser < ApplicationRecord
belongs_to :truck
belongs_to :user
end
I'm having a trouble with grasping a proper relationship between two Rails/ActiveRecord classes.
I have a User, which can both create a Slip and simultaneously be an addressee of another slip. Each user can create any number of slips but only one user as the addressee of a given slip.
From the db perspective I have two integer (key) columns for 'user_id' (author) and 'addressee' in the slips table and no reference on the users table.
This is my current approach which is not working at all:
class User < ApplicationRecord
has_many :slips
belongs_to :slips, :foreign_key => 'addressee'
end
class Slip < ApplicationRecord
belongs_to :user
has_one :addressee, :through => :user
end
Please direct me to the proper way of binding these objects.
Thanks and have a nice day!
It should look something like this (the second one could just be called 'slip' but I've called it 'addressor_slip' to avoid confusion):
class User < ApplicationRecord
has_many :slips
has_one :addressor_slip, :class_name=> 'Slip', :foreign_key => 'addressee'
end
class Slip < ApplicationRecord
belongs_to :user
belongs_to :addressee, :class_name=> 'User', :foreign_key => 'addressee'
end
You could also create an association from User to User through :addressor_slip
Look at many to many relationships here: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
You're probably looking for has_and_belongs_to_many.
I have the following associations in place:
class User < ActiveRecord::Base
has_many :shopping_requests
has_many :recommendations, :through => :shopping_requests
end
class ShoppingRequest < ActiveRecord::Base
belongs_to :user
has_many :recommendations
end
class Recommendation < ActiveRecord::Base
belongs_to :shopping_request
has_one :user, :through => :shopping_requests
end
Now I need to add a Compliment class. A user can compliment another user (so I have a user_from_id and a user_to_id). A compliment can be given for either a shopping request and/or a recommendation; and there's no limit (a user can be given several compliments by the same user or other users for any number of shopping requests as well as for recommendations).
I do know to make the Compliment polymorphic but not sure what is the best way to set it up in relation to users/shopping requests/recommendations.
I want to be able to run queries like this:
user_to_id.compliments (to get all the compliments for the user);
user_to_id.shopping_request.compliments (to get all that user's compliments for a particular shopping request;
user_to_id.recommendation.compliments (to get all that user's compliments for a particular recommendation; for this particular query, running user_to_id.shopping_request.recommendation.compliments is fine too);
user_from_id.compliments (to get all the compliments that a user gave another);
user_from_id.shopping_request.compliments (to get all the compliments given by this user for a particular shopping_request), etc....
So what is the best way to set up the association for the Compliment class?
Here's my first swing. Your already-written code works, and I haven't reproduced it here.
class User < ActiveRecord::Base
...
has_many :outgoing_compliments, class_name: "Compliment", foreign_key: "from_id"
has_many :incoming_request_compliments, through: :shopping_requests, source: compliments
has_many :incoming_recommendation_compliments, through: :recommendations, source: compliments
...
end
class ShoppingRequest < ActiveRecord::Base
...
has_many compliments, as: :compliment able
...
end
class Recommendation < ActiveRecord::Base
...
has_many compliments, as: :complimentable
...
end
class Compliment < ActiveRecord::Base
belongs_to :complimentable, polymorphic: true
#relies on two DB columns, complimentable_id and complimentable_type
belongs_to :complimenter, class_name: "User", foreign_key: "from_id"
end
I made one change to your database as you've defined it. Compliment knows which Complimentable it belongs to, and since each Complimentable knows its User, saving the complemented-User is redundant. You could choose to add the lines...
class Compliment
belongs_to :complimented, class_name: "User", foreign_key: "from_id"
class User
has_many :incoming_compliments, class_name: "Compliment", foreign_key: "to_id"
...but I don't think I would.
Those are the associations you'll need to create. However, some of your desired method calls aren't specific enough. One example:
user_to_id.shopping_request.compliments (to get all that user's
compliments for a particular shopping request;
Because what you've written is an instance method for User, we can assume the User is known. However, since a User can have many ShoppingRequests, it isn't possible, through what you've written, to hone in on one specific request to show Compliments for.
I am curious how I would go about implementing this.
I am creating an online learning website. There are a few courses on this website that users can complete. Courses have complete attribute which is just a boolean.
I want each user's progress to be trackable. So let's say I am at the course show page and I want to be able to do
#course.complete?
and get a unique response for each user.
Right now I have a user model set up which can log in and out, but I do not have a relationship between users and courses.
What the best way to set up this relationship so that each course is unique to each user?
i.e. if User A has complete the course then it will show true. If User B has not completed the course then it will show false.
Thanks!
I would do it like this
class Student < ActiveRecord::Base
has_many :course_enrollments
has_many :courses, :through => :course_enrollments
# code here
end
class Course < ActiveRecord::Base
has_many :course_enrollments
has_many :students, :through => :course_enrollments
# code here
end
class CourseEnrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
# code here
end