Ruby on Rails 3 Multiple Associations - ruby-on-rails

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.

Related

ActiveRecord: has_many choices limited to has_many of another model

I would like to achieve something as follows where PersonSubject has many topics, but the choices of these topics are limited to the the selection of topics through another model (ie: through the associated subject):
class Topic < ApplicationRecord
belongs_to :subject
end
class Subject < ApplicationRecord
has_many :topics
end
class PersonSubject < ApplicationRecord
belongs_to :person
belongs_to :subject
has_many :topics # where the choices are limited to the subject.skills
end
I would then like if any person_subject.subject.topics are deleted (or association removed), it would automatically update the person_subject.topics to no longer "point" to the Topic(s) that were deleted.
Is this possible?
You can use a lambda to put arbitrary filters on an association. See What is the equivalent of the has_many 'conditions' option in Rails 4?
has_many :topics, -> { where(skill: subject.skills) }
I don't know that this is exact code will work without seeing your schema (what is the data type of subject.skills, and how do you join this with topic?). But hopefully this gets you on the right track
edit
in response to your comment, I think
has_many :topics, through: :skills
would work

Rails multiple association types

Can you have a has_many belongs_to relationship AND a has_many :through relationship without any method call conflicts?
For example, what would user.hacks return - the hacks that the user posted, or the hacks that the user marked as favorites?
I need to be able to access both results.
class User < ActiveRecord::Base
has_many :hacks, through: :favorites
has_many :hacks
end
class Hack < ActiveRecord::Base
has_many :hacks, through: :favorites
belongs_to :users
end
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :hack
end
The has_many defines the methods depending on the relationship name.
This means, that if you are defining a relationship several times with the same name but different options, the last definition will override the methods of the previous definition calls.
So you can't define them with the same name.
If you need to access to both, hacks and favorite hacks, you have to create the relationships as follows
class User < ActiveRecord::Base
has_many :hacks
has_many :favorites
has_many :favorited_hacks, through: :favorites, source: :hack
end
I'm not sure what it will return, try it in the console.
However i would something like that in the user class (not tested, but you can look on how to alias relations):
has_many :favorite_hacks, through: :favorites, class: :hacks
There seems to be a typo in Hack.
On another note, the way you do it seems strange to me. The user hack relation seems like ownership, and the user - fav seems like bookmarking.
If all you want is to get a list of all favourite hacks (and all hacks belong to the user), then I think it would be a much better idea to model this behaviour like so:
class User < ActiveRecord::Base
has_many :hacks
end
class Hack < ActiveRecord::Base
belongs_to :user
scope :favorite, -> { where(favorite: true) }
end
and simply have a favorite attribute on your Hack model. Then you could find the favorites by invoking user.hacks.favorite.

Method to give number of favorites in has_many through relationship

I have a User model where the users can "favorite" each other. I'm achieving this through a Favoriting model as a has_many through relationship to reference User to itself:
class User < ActiveRecord::Base
has_many :favoriting
has_many :favorites, through: :favoritings, source: :favorited
has_many :favoriteds, class_name: "Favoriting", foreign_key: "favorited_id"
has_many :favoriters, through: :favoriteds, source: :user
...
end
class Favoriting < ActiveRecord::Base
belongs_to :user
belongs_to :favorited, :class_name => 'User'
...
end
This all works great. I can do u.favorites and get a user's favorites, and I can do u.favoriters to get the users that have favorited u. I can also do u.favorites_count to get the number of favorites.
However, I can't do u.favoriters_count to get the number of users that have favorited u.
Any idea if there is access to a built-in method for favoriters_count or even favoriteds_count with this type of DB relationship? I could write my own but would rather keep the code base as simple and "Rails-y" as possible.
Have you considered adding a counter_cache alongside with a favoritings_count column?
No, the methods added by has_many are listed in 4.3.1 of http://guides.rubyonrails.org/association_basics.html and do not include a method by this name.

How do I model this relationship for each unique user?

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

Rails data modeling - alternatves to has_many :through, given polymorphism?

I'm working on an application that allows users to associate images with specific events. Events are owned by a user. The easy solution would of course be:
class Image < ActiveRecord::Base
belongs_to :event
end
class Event < ActiveRecord::Base
has_many :images
belongs_to :user
end
class User < ActiveRecord::Base
has_many :events
has_many :images, :through => :events
end
Except! There is one problem. I also want my users to be able to own images directly, without an intermediary event. If I use polymorphic association "conventionally" here, then obviously user.images won't work properly. Should I just hold my nose, use an :as => :event_images to disambiguate, and define user.all_images if that need ever comes up? Should I have all images owned directly by users and optionally associated with events somehow? (With, of course, a validation to ensure consistency... but that seems code-smelly.) Is there a third solution that is prettier than either of these?
I often find it useful to forget the ActiveRecord DSL and work with the data model directly when defining complex relationships. Once the data model is correct you can then map it into model statements.
It is not quite clear from your question if Images can be owned by a User or an Event, of if they can be owned by both at the same time. That issue will determine the data model you use.
If an Image can be owned by both, the Image table will need a reference to both a user_id and an event_id, which may need to be nullable depending on your use-case (user or event being optional relationships). If the Image can only be owned by one, you could set up some sort of polymorphic ownerable relationship that maps owners to the right owner table (owner_id, owner_type etc etc).
Assuming it can belong to both:
class Image < ActiveRecord::Base
belongs_to :event
belongs_to :user
end
class Event < ActiveRecord::Base
has_many :images
belongs_to :user
end
class User < ActiveRecord::Base
has_many :events
has_many :images
has_many :event_images, :through => :events, :class_name => "Image"
end

Resources