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
Related
I have the following setup in my database. We have users. Each user has many entries. Users also belong to organizations, through a table called organization_users.
class User < ActiveRecord::Base
has_many :entries
has_many :organization_users
has_many :organizations, :through => :organization_users
end
class Entry < ActiveRecord::Base
belongs_to :user
end
class Organization < ActiveRecord::Base
has_many :organization_users
has_many :users, :through => :organization_users
end
class OrganizationUser < ActiveRecord::Base
belongs_to :user
belongs_to :organization
end
Here's my question: for a given organization, I want to get a list of all the entries for the users in that organization. Is there a nice compact way to accomplish this? I know I can iterate over all users in the organization and get the entries, but I'm not sure if there is a nice rails-y way to do this.
You can do the following assuming you have a foreign key called organization_id in organization_users table as per your Model
Entry.joins(:user => :organization_users).where("organization_users.organization_id = ?", org_id)
Where org_id is the id of the given organization. This will give you all entries of all users in an organization
Try something like that :
my_org = Organization.find(id)
my_org.users.eager_load(:entries)
First you get the organization you want to query. Then via :through association you can directly retrieve all the users for that organization. And last using eager_load, in one query you get all the entries. The result will be an ActiveRecord::Relation.
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'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
I'm in the process of trying to develop my first rails application and before I jump off into actually implementing my ideas, I was hoping I could get some help with my association planning.
My application is going to be an educational tool and I have some basic functionality that I need to implement. I want to have students that can register for and create courses. Within these courses, there will be one user who is the teacher and the rest are students. Within the course will be assignments that the teacher creates and that users are required to make a submission for. These are the basics. Eventually I want to add more functionality but as of now I just need the foundations set.
Here are my ideas so far on what the associations should look like:
class User < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollments
end
class Course < ActiveRecord::Base
has_many :enrollments
has_many :users, :through => :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :user # foreign key - user_id
belongs_to :course # foreign key - course_id
end
However, I'm running into my wall of inexperience at the moment with how to appropriately handle the rest of the associations at this point. I could probably hack out something, but I'd prefer to do it as best as I can the first time.
How do I handle the associations related to assignments and submissions? Presumably a student's submission should belong_to them, but it is also specifically related to an assignment within the class. Assignments originate within a course, but are also closely tied to the user.
Finally, what's the best way to handle the relationships between a user and the class they create? A user creates a course. Does that course belong_to them? Should I just add a column to the course's table for storing the creator's id? Etc.
Thanks for the help.
Suggestion:
You might want to separate out your Teacher and Student models, since you're very likely to have different actions associated with each (and while they share some attributes, they really are different entities in your model, for example, you likely want just one teacher teaching in a course.)
You could derive both the Teacher model and the Student model from a User model that has the shared attributes and authentication.
Now to your questions:
If you'd like to keep the student that created the course associated, creator_id is the way I'd go. (If a teacher can create a course too, deriving Student and Teacher from a shared User model would help)
Assignments and Submissions:
You've pretty much defined it in words. Here is the code.
[If different students within a course could get different assignments, only then do you want to build a direct association between Student and Assignment, otherwise, use a through association]
class User < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollments
has_many :assignments, :through => :courses
has_many :submissions
end
class Course < ActiveRecord::Base
has_many :enrollments
has_many :users, :through => :enrollments
has_many :assignments
end
class Enrollment < ActiveRecord::Base
belongs_to :user # foreign key - user_id
belongs_to :course # foreign key - course_id
end
class Assignment < ActiveRecord::Base
belongs_to :course
has_many :submissions
end
class Submission < ActiveRecord::Base
belongs_to :assignment
belongs_to :user
end
1)A user can have many causes and a cause can belong to many users.
2)A user can have many campaigns and campaigns can belong to many users. Campaigns belong to one cause.
I want to be able to assign causes or campaigns to a given user, individually. So a user can be assigned a specific campaign. OR a user could be assigned a cause and all of the campaigns of that cause should then be associated with a user.
Is that possible? And could I set it up so that the relationships could be simplified like so:
User.causes = all causes that belong to a user
User.campaigns = all campaigns that belong to user whether through a cause association or campaign association
This should work.
class User < ActiveRecord::Base
has_many :causes, :through => :cause_users
has_many :campaigns, :through => :campaign_users
# other model stuff
class Cause < ActiveRecord::Base
has_many :users, :through => :cause_users
has-many :campaigns
# other model stuff
class Campaign < ActiveRecord::Base
belongs_to :cause
has_many :users, :through => :campaign_users
# other model stuff
class CampaignUser < ActiveRecord::Base
belongs_to :campaign
belongs_to :user
# other model stuff
class CauseUser < ActiveRecord::Base
belongs_to :cause
belongs_to :user
# other model stuff
has_many :through requires that you create a new model for each of these joins: campaign_users and cause_users, as is shown but it provides more functionality later on than has_and_belongs_to_many.
I would also suggest using better names than :campaign_users and :cause_users so the relationship is more meaningful.
I believe you should use the following:
class User < ActiveRecord::Base
has_and_belongs_to_many :causes
has_and_belongs_to_many :campaigns
end
class Cause < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :campaigns
end
class Campaign < ActiveRecord::Base
has_and_belongs_to_many :users
belongs_to :cause
end
This way you can use
User.causes
User.campaigns
Cause.campaing
Cause.users
Campaign.users
Campaign.cause
You can read here about has_and_belongs_to_many relationship, here about has_one and here about belongs_to.
Let me know if this is what you want :]
Edit:
"I would still need User.campaigns to
be campaigns from a user's causes or
individual campaigns associated with a
user"
You can have a method on users model that returns all campaigns. Something like this:
def all_campaigns
self.campaigns + self.causes.collect{ |c| c.campaigns }
end
You can make :has_many :through associations between users and campaigns using a join model, and also between users and causes using the another join model. The you can make a :has_many :campaigns association in the causes model, putting a :belongs_to :cause in the campaign model.
But you won't be able to fetch all the users campaigns or causes by User.campaigns.orders or User.order.campaigns. You should make an iteration over the User.campaigns collection or User.causes, fetching Campaign.cause or Cause.capaigns. Or even making a custom SQL query, using joins and conditions to filter information in the joins.