Join Table Confusion Ruby on Rails - ruby-on-rails

I have read a lot of the questions and answers here about join tables, STI tables, and polymorphic associations, in addition to many articles and documentation spread throughout the internet. While I've learned a lot I'm still confused about what I should do in my situation. I may have read the answer and not known I was reading the answer, but I wanted to see if someone could help me understand what it is I should do here.
I have a Gallery model, an Album model, an Image model and a Category model. These are all nested in a User model.
When you create an Album assign a Category to it and those are saved with an Album_Categories model. I want the Gallery model to be aware of what Categories exist and be able to choose which ones it would like to use.
Once it selects a Category, it should be able to access the Albums associated with the Category and the Album's Images, which are linked through and Album_Images join table. The Category should be able to continue to exist even if the Album or Gallery that it was originally created with is deleted so that another Album or Gallery can take advantage of it later.
My sense is that whenever a unique Category is created is should some how connect to Gallery through a Category_Galleries model, but in my use of Images which is connected to Gallery and Album with their own specific join tables, Gallery is unaware of an Album_images Connection, so I assume sharing the knowledge of a Category created by the other would be the same.
Any way to help me unerstand this would be appreciated.
Edit: model code
class User < ActiveRecord::Base
has_many :images, dependent: :destroy
has_many :galleries, dependent: :destroy
has_many :albums, dependent: :destroy
has_many :categories, dependent: :destroy
accepts_nested_attributes_for :images, :galleries, :albums, :categories, allow_destroy: true
accepts_attachments_for :images, attachment: :file, append: true
end
class Image < ActiveRecord::Base
belongs_to :user
has_many :gallery_images, dependent: :destroy
has_many :galleries, through: :gallery_images
has_many :album_images, dependent: :destroy
has_many :albums, through: :album_images
attachment :file, type: :image
validates :file, presence: true
end
class Album < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
has_many :album_galleries
has_many :galleries, through: :album_galleries # , dependent: :destroy
has_many :album_images, dependent: :destroy
has_many :images, through: :album_images
has_many :album_categories
has_many :categories, through: :album_categories
accepts_attachments_for :images, attachment: :file, append: true
accepts_nested_attributes_for :images
end
class Gallery < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
has_many :gallery_images, dependent: :destroy
has_many :images, through: :gallery_images
has_many :album_galleries, dependent: :destroy
has_many :albums, through: :album_galleries
accepts_attachments_for :images, attachment: :file, append: true
accepts_nested_attributes_for :images
end
class Category < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
has_many :albums, through: :album_categories
has_many :album_categories
end
class GalleryImage < ActiveRecord::Base
belongs_to :gallery
belongs_to :image
end
class AlbumCategory < ActiveRecord::Base
belongs_to :category
belongs_to :album
end
class AlbumGallery < ActiveRecord::Base
belongs_to :gallery
belongs_to :album
end
class AlbumImage < ActiveRecord::Base
belongs_to :album
belongs_to :image
end

It really depends on the requirements you're trying to model. Does this accurately reflect your requirements? (ignoring the user for the current moment and not necessarily detailing rails associations)
A gallery can consist of many categories
A category can contain many albums
An album can have many images
If so, you could simply have:
a has_many through association between galleries and categories
a has_many through association between albums and categories
a has_many through association between albums and images
The has_many through will allow your categories, galleries, albums and images to exist even after relations are destroyed.
At the moment I don't see any need for STI or polymorphism. Usually you use polymorphic associations when two models share (own) the same table. But since you would use has_many through associations, polymorphism wouldn't even be necessary. (It prevents clashes of the owning table ids when occuring as a foreign key in the owned table).
To get to images, from gallery, for example you would be essentially displaying all the images of all albums belonging to all categories that are assigned to a gallery. That can be done through associations and querying.
So basically, I don't think your scenario...based on my understanding...is too complex and has_many through associations should suffice.
An interesting question would by why a user is associated to all of your models. Are they responsible for creating/those model instances a user is associated to?

Related

Related content in Rails. User selects the content to relate. How to do? HasMany? BelongsTo?

The application have Posts, Products and Services. I want the user selects the specific content to relate. Example:
I have 2 services. And I'm adding a new PRODUCT. In this product, I want to relate these 2 services and other 1 product.
The first thing I thought is to create a field in the database like related_content in all resources and save the ids with comma, like it: service_25, service_302, product_408. I did it other times, and... works.
I save the prefix service_ and product_ because the same item can be related with products and services.
But I think it is not the right way. Perhaps the right way is to use the many to many association. But I don't know how to do.
MODELS
product.rb
class Product < ActiveRecord::Base
has_many :menu_assigns, as: :menu_item, dependent: :destroy
has_many :categorizings, as: :item, dependent: :destroy
has_many :categories, -> { where for: Category.fors[:for_products] }, through: :categorizings
has_one :attach, as: :attached, dependent: :destroy
has_one :attachment, through: :attach
end
service.rb
class Service < ActiveRecord::Base
has_many :menu_assigns, as: :menu_item, dependent: :destroy
has_many :categorizings, as: :item, dependent: :destroy
has_many :categories, -> { where for: Category.fors[:for_services] }, through: :categorizings
has_one :attach, as: :attached, dependent: :destroy
has_one :attachment, through: :attach
end
post.rb
class Post < ActiveRecord::Base
has_many :menu_assigns, as: :menu_item, dependent: :destroy
has_many :categorizings, as: :item, dependent: :destroy
has_many :categories, -> { where for: Category.fors[:for_posts] }, through: :categorizings
has_one :attach, as: :attached, dependent: :destroy
has_one :attachment, through: :attach
end
This code has already some associations:
Menu: Using menu_assigns, the user can add Post, Product and Service to menu.
Category: The resources has categories. Using categorizings, the content is related.
Attachment: Is the featured image. Using attach we relate an image.
But, how to relate each other using the associations?
I imagine something like it: #page.related_content and returns an object with the registers.
My idea
1) Create a model called related_groups with these fields:
rails g model RelatedGroup item_id:integer item_type:string order:integer
Item ID is the ID of the related content. The item Type is the model (Product, Service, Post). The order field is the order to show.
2) In that model, create the relation:
class RelatedGroup < ActiveRecord::Base
belongs_to :item, polymorphic: true
end
3) Do the relation in the resources (Product, Service, Post). Below the example to post:
class Post < ActiveRecord::Base
has_many :related_groups
end
4) Join the results in all models (Product, Service, Post). Below the example to post:
class Post < ActiveRecord::Base
has_many :related_groups
with_options through: :related_groups, source: :item do
has_many :posts, source_type: 'Post'
has_many :products, source_type: 'Product'
has_many :services, source_type: 'Service'
end
end
This seems to be correct, but I'm not sure.
And the controllers? And the views?
In Post (example), how to create the checkboxes to check the related contents? And in the controller, how to save the data? And how to set the order?
I'm using rails 4.2
I appreciate any help. Tks!
Check the has_and_belongs_to_many association:
https://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
Depending on your requirements you could either have one join table for each kind of related object (Post/Product/Service) or have a combined join table with an additional attribute to distinguish what kind of object you are associating with, e.g.
class CreatePostRelationJoinTable < ActiveRecord::Migration
def change
create_table :post_relations, id: false do |t|
t.integer :post_id
t.string :relation_type
t.integer :relation_id
end
end
end

get object using has many through with polymorphic association

I am using rails 5.
Here I am using has many through with polymorphic
Tables are
categories, category_items, albums
Models are
Category, CategoryItem, Album
app/model/album.rb
class Album < ApplicationRecord
has_many :category_items, as: :category_itemable, dependent: :destroy
has_many :categories, through: :category_items
end
app/model/category.rb
class Category < ApplicationRecord
has_many :category_items, dependent: :destroy
end
app/model/category_item.rb
class CategoryItem < ApplicationRecord
belongs_to :category_itemable, polymorphic: true, optional: true
belongs_to :category, optional: true
end
I am able to get categories of a specific album through association. but now I need to get albums of a specific category.
Please find the solution and make sure don't use method. association should be clean and simple
Many Thanks in Advance
You just need to add a has_many association for album in Category model
class Category < ApplicationRecord
has_many :category_items, dependent: :destroy
has_many :albums, through: :category_items, source: :category_itemable, source_type: 'Album'
end
And now you will be able to get albums for a category instance

Is it good practice to validate IDs in a join model?

I have a HMT association setup between my Artist and Group models:
class Artist < ApplicationRecord
has_many :artist_groups, dependent: :destroy
has_many :artist_groups, through: :artist_groups
end
class ArtistGroup < ApplicationRecord
has_many :memberships, class_name: "ArtistGroupMembership", dependent: :destroy
belongs_to :artist
belongs_to :group
has_and_belongs_to_many :roles
accepts_nested_attributes_for :memberships, reject_if: :all_blank, allow_destroy: true
validates_presence_of :artist_id, :group_id
end
class Group < ApplicationRecord
has_many :artist_groups, dependent: :destroy
has_many :members, through: :artist_groups, source: :artist
end
As you'll notice in my ArtistGroup join model it validates to make sure the an artist and group are present.
When the association is saved, whether I do something like this:
artist.groups.push(Group.first)
or create a form in my view (sans ID inputs) ActiveRecord is smart enough to map the association. With this in my should I even be validating these IDs in my join models? I notice this becomes even more of a pain when dealing with polymorphic associations.
Rails 5 automatically requires that the belongs_to :artist refers to an existing artist so having extra validation is completely unnecessary. You can make that requirement optional by doing
belongs_to :artist, optional: true

Including attributes from a join model in a seperate form field

I have an association setup where a Track can belong to multiple Releases through ReleaseTracks:
class Release < ApplicationRecord
has_many :release_tracks, dependent: :destroy
has_many :tracks, through: :release_tracks
end
class ReleaseTrack < ApplicationRecord
# disc, side, number
belongs_to :release
belongs_to :track
belongs_to :album
end
class Track < ApplicationRecord
has_many :release_tracks, dependent: :destroy
has_many :releases, through: :release_tracks
validates_uniqueness_of :name, scope: [:release_id, :album_id]
end
As you can see, the ReleaseTrack has three attributes disc, side, number to correctly identify the correct placement for a track on any specific release. I was wondering in the instance of creating a new track in a release form, how could I also save those three attributes?

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 }

Resources