Multiple has_many: :through relationship to association - ruby-on-rails

I have a model for Sellers and they own games and unlimited_games which can be bought by other players. I want to pull all the purchases for a specific seller. My model when I only had games was:
class Seller
has_many :purchases, through: :games, dependent: :destroy
end
class Purchase
belongs_to :game
end
class Game
has_many :purchases
belongs_to :coach
end
Updated Models
def UnlimitedGame
has_many :purchases
belongs_to :coach
end
def Purchase
belongs_to :game
belongs_to :unlimited_game
end
And I could do a #seller.purchases to get all purchases for that seller. Now I want to extend the same feature to include all unlimited_games
So that doing #seller.purchases includes both unlimited_games and games. I tried doing:
has_many :purchases, through: :games, dependent: :destroy, class_name: :purchases
has_many :purchases, through: :unlimited_games, dependent: :destroy, class_name: :purchases

Supussing unlimited_games is not a model on itself and instead is a scope on the games model, then you could do something like this in the Seller model:
def unlimited_game_purchases
purchases.merge(Game.unlimited)
end
which uses merge to merge the condition of both queries
By the way, there is of course a way to create another association with the same model, if as you say the seller also has a has_many :unlimited_games already working, then your association will look like:
has_many :unlimited_game_purchases, trough: :unlimited_games, source: :game
You can choose the way you understand better

Related

Rails Complex Model Association, Shared Document Between Users and Teams

I have a complex model association in mind, and was wondering how I could accomplish it. This is what i want to accomplish.
I have a User and a Document model
A User can create documents. He is now the document admin.
He can add other users to his document, and give them permissions such as Editor, Viewer, Admin
He can also make a team, a group of users, and add multiple teams to his document. Each user on a team that the User has added to his document will also have permissions. A user can belong to many teams.
I am a little bit confused about the associations I will have to setup. This is the code I have so far, which has not incorporated the team aspect:
class User < ApplicationRecord
has_many :participations
has_many :documents, through: :participations
end
class Document < ApplicationRecord
has_many :participations
has_many :users, through: :participations
end
class Participation < ApplicationRecord
belongs_to :user
belongs_to :document
enum role: [ :admin, :editor, :viewer ]
end
I would recommend introducing a Team and TeamMembership models in a similary way to existing models. Also change the belongs_to association on Participation from user to a polymorphic participant.
class Team < ApplicationRecord
has_many :team_memberships
has_many :users, through: :team_memberships
has_many :participations, as: :participant
end
class TeamMembership < ApplicationRecord
belongs_to :team
belongs_to :user
end
class User < ApplicationRecord
has_many :team_memberships
has_many :teams, through: :team_memberships
has_many :participations, as: :participant
end
class Participation < ApplicationRecord
belongs_to :participant, polymorphic: true
belongs_to :document
enum role: [ :admin, :editor, :viewer ]
end
class Document < ApplicationRecord
has_many :participations
# if desired create a users method to conveniently get a list of users with access to the document
def users
#users ||= participations.flat_map do |participation|
case participation.partipant
when User
[participation.participant]
when Team
participation.participant.users
end
end
end
end
I would only add has_many :through associations as you discover a benefit/need to having them. That will reduce complexity of maintaining them unless you have specific use case for them. In the case of User having a teams association, it's pretty obvious that you'll be likely to want to get the teams that the user is a part of and since there's no specific information in the TeamMembership object that you are likely to need in that determination, it's a good has_many :through to have.
EDIT: Added Document model.
Since you already have a participation model, you can use that as the join model between users and teams. Since a user can belong to multiple teams, and a document can have multiple teams, you can use a has_many through relationship between teams and documents. We'll call it the DocumentTeam model.
class User < ApplicationRecord
has_many :participations
has_many :documents, through: :participations
has_many :teams, through: :participations
end
class Participation < ApplicationRecord
belongs_to :document
belongs_to :user
belongs_to :team, optional: true
enum role: [ :admin, :editor, :viewer ]
end
class Team < ApplicationRecord
has_many :participations
has_many :users, through: :participations
has_many :document_teams
has_many :document, through: :document_teams
end
class Document < ApplicationRecord
has_many :participations
has_many :users, through: :participations
has_many :document_teams
has_many :teams, through: :document_teams
end
class DocumentTeam < ApplicationRecord
belongs_to :document
belongs_to :team
end

Ruby: specify has_many :through association when several has_many :through associations are given

i have a simple data set-up with a model for Users and a model for Tasks.
Between these two models i have two has_many :through associations: Fellowships and Assignements. In total i want to specify for a task several followers and several assignees.
I now want to display for a specific task all assignees and all followers.
If there would only be one association I simply could do #task.users. As i have two associations i want to specify by which association i want to get all users.
See my code:
class User < ActiveRecord::Base
has_many :assignments
has_many :tasks, through: :assignments
has_many :fellowships
has_many :tasks, through: :fellowships
end
class Task < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
has_many :fellowships
has_many :users, through: :fellowships
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class Fellowship < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
Let's assume i have a task as
#task = Task.first
I now want to have all assignees and all followers with something like
#assignees = #task.users "over association assignment"
#followers = #task.users "over association followship"
but i don't know how to do this.
Thanks for the help!
You can write in following way.
has_many :assignment_tasks ,through: :assignments ,source: :task
has_many :fellowship_tasks, through: :fellowships, source: :task

A model that belongs_to two models or a polymorphic association?

I have three models
User
Product
Order
A user who submits products and other users can order that product.
Now I want to implement another model payment, where I as an admin pay the user.
Question
I am confused about what kind of association should i create between user, order and payment?
This is what I currently have :
in app/models/user.rb
class User < ActiveRecord::Base
has_many :products_selling, class_name: 'Product'
has_many :orders_received, class_name: 'Order', through: :products_selling, source: :orders
has_many :orders_made, class_name: 'Order'
has_many :products_ordering, class_name: 'Product', through: :orders_made, source: :product
has_one :payment, class_name: 'Payment', through: :orders_received
and in app/models/order.rb
class Order < ActiveRecord::Base
belongs_to :product
belongs_to :buyer, class_name: 'User', foreign_key: :user_id
has_one :seller, class_name: 'User', through: :product
has_one :payment, through: :seller
and in app/models/payment.rb
class Payment < ActiveRecord::Base
belongs_to :user
belongs_to :order
I am not sure what association should be used, I have been reading and there are examples using polymorphic: :true but they were all with has_many, where as in my case one order corresponds to one payment.
Associations
Polymorphic associations basically allow you to use a single table to manage multiple associations:
So if you wanted to link multiple tables to a single table, it will essentially create a pseudo object, which can be associated with different data types. We typically use polymorphic associations with tables which can be used by multiple models, examples including images, errors, posts etc:
In regards to relating polymorphic associations to has_many / has_one, I'm not entirely sure (I can research if you want)
--
Fix
In your case, I would do this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :orders
has_many :purchases, class_name: 'Order', foreign_key: "buyer_id"
has_many :products
has_many :bought, class_name: 'Product', through: :purchases
end
#app/models/order.rb
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :product
has_one :payment
end
#app/models/payment.rb
Class Payment < ActiveRecord::Base
#fields - id | user_id | order_id | created_at | updated_at
belongs_to :order #-> user pays for order
belongs_to :user #-> user making the payment
end
This will allow you to create payments for each order - which is really what payments are for, right? (You pay for the order, not the product)

Rails: ignoring duplicates in an nested association

I have models User, Team, Document. There's a many-to-many relationship between Users and Teams, and a many-to-many relationship between Teams and Documents, using join tables called TeamMembership and TeamDocument respectively.
The relationships in my models look like this:
class Document < ActiveRecord::Base
has_many :team_documents, dependent: :destroy
has_many :teams, through: :team_documents
end
class User < ActiveRecord::Base
has_many :team_memberships, dependent: :destroy, foreign_key: :member_id
has_many :teams, through: :team_memberships
has_many :documents, through: :teams
end
class TeamDocument < ActiveRecord::Base
belongs_to :team
belongs_to :document
end
class TeamMembership < ActiveRecord::Base
belongs_to :team
belongs_to :member, class_name: "User"
end
class Team < ActiveRecord::Base
has_many :team_documents, dependent: :destroy
has_many :documents, through: :team_documents
has_many :team_memberships, dependent: :destroy
has_many :members, through: :team_memberships
end
The idea is that users can belong to many teams, a document can be associated with many teams, and users will only have access to documents that "belong" to at least one team that the user is a member of.
Here's the question: I can use User#documents to retrieve a list of all the documents that this user is allowed to view. But this will return duplicates if a document is viewable by more than one team which the user is a member of. How can I avoid this?
I know I can remove the duplicates after the fact with #user.documents.uniq, but as I will never want to include the duplicates in any case, is there a way I can just make #documents not include duplicates every time?
I don't have nested has_many :through like yours to test it, but I suspect using uniq option on your user association would help :
class User < ActiveRecord::Base
has_many :documents, through: :teams, uniq: true
end
You can add a default_scope on Document model:
class Document < ActiveRecord::Base
default_scope group: { documents: :id }

Rails: Polymorphic has_many: through workarounds

My app allows users to follow several different model types, including other users, organizations, products, etc. Each of these models has a has_many: :histories association. My goal is to compile all the :histories of the resources the current_user is following.
My models look like this:
class Follow < ActiveRecord::Base
belongs_to :user
belongs_to :followable, polymorphic: true
end
class History < ActiveRecord::Base
belongs_to :historical, polymorphic: true
end
class User < ActiveRecord::Base
has_many :follows
has_many :followed_resources, through: :follows, source: :followable
has_many :followed_histories, through: :followed_resources, source: :histories
has_many :followings, class_name: "Follow", as: :followable
has_many :histories, as: :historical
end
class Product < ActiveRecord::Base
has_many :followings, class_name: "Follow", as: :followable
has_many :histories, as: :historical
end
class Organization < ActiveRecord::Base
has_many :followings, class_name: "Follow", as: :followable
has_many :histories, as: :historical
end
etc
My goal is to get the histories from all the current_user's followed resources like so:
#histories = current_user.followed_histories
But unfortunately, Rails does not allow us to traverse polymorphic association in a has_many: through relationship. Instead, it insists that we specify only a single association using the source_type option. For instance
has_many :followed_products, through: :follows, source: :followable, source_type: :product
Unfortunately, this approach won't work in this case, unless there's some way to recombine all the associations afterwards. For instance, if there was some way to do this:
has_many :followed_histories, through: {:followed_products, :followed_organizations, :followed_users}
Or perhaps there's yet another approach I'm not considering. I'm open to any suggestions, so long as the end result is a single array containing the combined histories of the followed resources.

Resources