Rails Associations for Lookup Table - ruby-on-rails

I have a Statuses table which contains only an id and name field (Active, Inactive, Pending, etc). I then have tables such as Users, Achievements, Badges for which each of these contain a status_id foreign key. Do the associations in my models look correct?
class Status < ActiveRecord::Base
has_many :achievements
has_many :badges
has_many :users
end
class User < ActiveRecord::Base
belongs_to :status
end
class Badge < ActiveRecord::Base
belongs_to :status
end
class Achievement < ActiveRecord::Base
belongs_to :status
end
I am struggling with how to properly read the difference between has_one and has_many in the case of a lookup table. I know that a user has one company and has one profile and a company has many users but this seems backwards to me.

The simplest association setup would be:
class User < ActiveRecord::Base
has_one :status
end
That exactly describes what you have posted. Your solution would work, but it is overkill for what you've described. All the association I posted above would do is add one method to the user model, i.e.
#user = User.find(1)
#user.status
If on the other hand you wanted simple semantics for showing all the users with a particular status, THEN you'd add
class Status < ActiveRecord::Base
has_many :users
end
so now you could do:
#status = Status.find_by_description('Active').first()
#status.users
Note that in BOTH cases, all that is needed is for the users model to have an attribute 'status_id'
Belongs_to is better suited when there is an implicit hierarchy , i,e,
class Child << ActiveRecord::Base
belongs_to :parent
end

Related

Validating combination of columns not on the same table

There are 4 tables present: Course, Section, Enrollment, Student.
A Course has_many Sections
Sections and Students share a many_to_many relationship where Enrollment is the join table
I am trying to add a validation in Enrollment so that a student can't be enrolled into multiple Sections of the same Course. I can't figure out how to implement this validation because Course is not immediately related to Enrollment.
Here are the models for further clarification:
class Course < ActiveRecord::Base
has_many :sections
end
class Section < ActiveRecord::Base
belongs_to :course
has_many :enrollments
has_many :students, through :enrollments
end
class Enrollment < ActiveRecord::Base
validate :noDuplicateCourses
def noDuplicateCourses
if #TRYING TO FIGURE OUT HOW TO EXPRESS THE LOGIC HERE
errors.add(:student_id, 'Already enrolled in a different section of this course')
end
end
belongs_to :section
belongs_to :student
end
class Student < ActiveRecord::Base
has_many :enrollments
has_many :sections, through :enrollments
end
You can simply store the course_id on Enrollment. This method is called de-normalization, meaning that you have duplicate content which you try to avoid in relational databases.
Something like this should work. Just put your logic in place of your_logic_here.
validate :multiple_enrollment
private
# Custom validator
def multiple_enrollment
if your_logic_here
errors.add(self.class.table_name, "Student is already enrolled in another section of this course.")
end
end

Rails: Multiple relationships between two models

I'm creating a Database Migration in Rails 4.0.4, and I want to capture the following relationship:
A customer has many credit cards. A customer has only one default credit card.
and here's what I think it should look like.
class Customer < ActiveRecord::Base
has_many :cards
has_one :card # i.e. has one default card
end
class Card < ActiveRecord::Base
belongs_to :customer
end
Is this correct? If so, how does Rails know which relationship the belongs_to in the Card class refers to? If it's incorrect (and I'm guessing it is), please help me fix it.
I'd put the scope on the card's side, seems easier for me
class Customer < ActiveRecord::Base
has_many :card
end
class Card < ActiveRecord::Base
belongs_to :customer
scope :default, -> { where is_default: true }
end
default_card = customer.cards.default
Currently your code is enough to confuse Rails by having has_one :card and has_many :cards.You should be using class_name option provided specially for these type of associations.
Something like this should work for you
class Customer < ActiveRecord::Base
has_many :cards
has_one :default_card, :class_name => "Card"
end
foreign_key
To add to Pavan's answer, you'll need to use some sort of condition to determine which is the default card.
Because Rails' relational database structure relies on foreign_keys to pull the related data, you'll need to either assign the correct foreign_key for your default_card, or use a condition to find it:
#app/models/customer.rb
Class Customer < ActiveRecord::Base
has_one :default_card, -> { where default: true" },class: "Card", foreign_key: "customer_id"
end
This would rely on having the boolean column default in your cards table

Difference between has_many, belong_to, and both

What's the difference in the following three scenarios?
#Case 1
class User < ActiveRecord::Base
has_many :comment
end
class Comment < ActiveRecord::Base
belong_to :user
end
Case 1 has both has_many and belong_to.
#Case 2
class User < ActiveRecord::Base
has_many :comment
end
class Comment < ActiveRecord::Base
end
Case 2 has only has_many.
#Case 3
class User < ActiveRecord::Base
end
class Comment < ActiveRecord::Base
belong_to :user
end
Case 3 has only belong_to.
Since both has_many and belong_to represent a one-to-many relation, how to we decide which of these three we should use?
They require the same database schema. The difference is only which methods are defined for you.
When you add has_many :comments to User, you gain the ability to refer to user.comments, and so easily find the comments for a particular user object (and create new ones with user.comments.build, and so on).
When you add belongs_to :user to Comment, you gain the ability to refer to comment.user, and so find the user to whom a particular comment object belongs.
These calls simply create convenience methods for you to use when manipulating your model objects. I would suggest using both, because you will likely want to use both and the relationship is clearer to someone reading the code.

Can someone help me with this association?

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

Ruby on Rails Model / Database Associations

I have a user model, farmer model, doctor model, and education model.
A farmer has a user and many educations.
A doctor has a user and many educations.
How do I setup the database for the education model?
Should it have a farmer_id AND a doctor_id?
But a education cannot belong to a farmer AND and doctor at the same time. It's one or the other.
So my education database entry would either have a farmer_id OR a doctor_id filled in, but not both.
Is there a way to guarantee that only one of the ids could be filled in at a time?
Or is there a better way to associate these models?
Your help would be appreciated!
Oh, and don't worry about the names of the models (farmer, doctor, etc.). It's just an example.
I see two possible solutions for this scenario.
The first one is to make use of polymorphic associations for education. That could look like this:
class Farmer < ActiveRecord::Base
belongs_to :user
has_many :educations, :as => :profession
end
class Doctor < ActiveRecord::Base
belongs_to :user
has_many :educations, :as => :profession
end
class Education < ActiveRecord::Base
belongs_to :profession, :polymorphic => true
end
So instead of education having a doctor_id or a farmer_id it has one profession_id and one profession_type.
The second solution would be to make use of Single Table Inheritance. And in your scenrio, that could be accomplished by letting a Doctor be a User instead of belonging to a User. And of course the same thing for a Farmer. That could look like this:
class User < ActiveRecord::Base
has_many :educations
end
class Farmer < User
end
class Doctor < User
end
class Education < ActiveRecord::Base
belongs_to :user
end
And in this scenario you would add a type column to the User model to store what type of class it is and then only having a user_id in the Education model
I think its appropriate to have the relations this way based on roles.
Class User
has_one :role
has_many :educations
end
Class Role
#What ever roles you have.
#Farmer or Doctor
belongs_to :user
end
class Education
belongs_to :user
end
This way you will store the user_id in the education object, which solves your problem.

Resources