Polymorphic has_and_belongs_to_many - ruby-on-rails

How define a has_and_belongs_to_many polymorphic association?
Situation:
Figure that we have users, tracks, lists, etc... and all these models can be tagged and use this tag for filter.
What i'm trying to do is:
Use has_and_belongs_to_many that enable to a tag to have other objects, and that other objects also can have other tags.
So for enable a tag that belongs to more than one kind of object (users or tracks or tracklists), we need to use polymorphic relations.
That's my code on this moment:
tags migration
class CreateTags < ActiveRecord::Migration[5.2]
def change
create_table :tags do |t|
t.string :name
t.references :taggable, polymorphic: true, index: true
t.timestamps
end
create_table :assemblies_parts, id: false do |t|
t.belongs_to :assembly, index: true
t.belongs_to :part, index: true
end
end
end
tag model
class Tag < ApplicationRecord
has_and_belongs_to_many :taggable, polymorphic: true
end
user model
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
has_and_belongs_to_many :tags, as: :taggable
has_one :top_list
has_many :tracks, through: :top_list
end
track model
class Track < ApplicationRecord
has_and_belongs_to_many :tags, as: :taggable
belongs_to :top_list
end

You can use a has_many association:
Tag model
class Tag < ApplicationRecord
has_many :relation
end
Relation model
class Relation < ApplicationRecord
belongs_to :tag
belongs_to :taggable, polymorphic: true
end
User model
class User < ApplicationRecord
has_many :relations, as: :taggable
has_many :tags, through: :relations
has_one :top_list
has_many :tracks, through: :top_list
end
Track model
class Track < ApplicationRecord
has_many :relations, as: :taggable
has_many :tags, through: :relations
belongs_to :top_list
end
Tags migration
class CreateTags < ActiveRecord::Migration[5.2]
def change
create_table :tags do |t|
t.string :name
t.timestamps
end
create_table :relations, id: false do |t|
t.references :tags, index: true
t.references :taggable, polymorphic: true, index: true
end
end
end

Related

Rails: Favoriting System - models associations

I have the following model associations setup were a consumer can favorite a product or a variant. I just wanted to ask if my approach is correct?
class Favorite < ApplicationRecord
belongs_to :consumer
belongs_to :favorited, polymorphic: true
belongs_to :product, optional: true
belongs_to :variant, optional: true
end
class Consumer < ApplicationRecord
has_many :favorites
has_many :favorite_products, through: :favorites, source: :favorited, source_type: 'Product'
has_many :favorite_variants, through: :favorites, source: :favorited, source_type: 'Variant'
end
class Product < ApplicationRecord
has_many :favorites, dependent: :destroy
end
class Variant < ApplicationRecord
has_many :favorites, dependent: :destroy
end
class CreateFavorites < ActiveRecord::Migration[6.0]
def change
create_table :favorites do |t|
t.references :consumer, index: true
t.references :favorited, polymorphic: true, index: true
t.integer :product_id
t.integer :variant_id
t.timestamps
end
end
end
It is not clear why you have both polymorphic favorited and separate relation on product_id/variant_id. Usually one would go with one of these approaches.
Polymorphic associations can be unidirectional too - has_many :favorites, as: :favorited, dependent: :destroy
If you go with separate columns for each relation - indexes on product_id and variant_id may also be useful to prevent full table scan on product/variant deletion (on dependent: :destroy, also if you do not expect callbacks/nested relations there - delete_all is faster).

Rails: How to handle associations among namespaced models?

I am trying to figure out what's the best way to handle namespaced models. Here's the models that i have in my project:
class Member < ApplicationRecord
has_one :ledger, inverse_of: :member, class_name: "Member::Ledger", dependent: :destroy
has_many :ledger_entries, through: :ledger
end
class Member::Ledger < ApplicationRecord
belongs_to :member, inverse_of: :ledger
has_many :ledger_entries, foreign_key: "member_ledger_id", dependent: :destroy
end
class Member::LedgerEntry < ApplicationRecord
belongs_to :ledger, foreign_key: "member_ledger_id"
end
And here's how my migrations files look like:
create_table :members do |t|
t.timestamps
end
create_table :member_ledgers do |t|
t.references :member, foreign_key: true, null: false, index: { unique: true }
t.timestamps
end
create_table :member_ledger_entries do |t|
t.references :member_ledger, foreign_key: true, null: false
t.timestamps
end
So I have few questions here:
Are migration files correct? I mean should i have member_ledger_id in the member_ledger_entries table or just ledger_id?
Are associations defined in a correct way? Even though this works but i am not sure this is how we are supposed to proceed.
I am using ruby-2.5.1 and rails-5.2.0.
Any help would be appreciated. Thanks in advance !!
Perhaps your associations could look more like:
class Member < ApplicationRecord
has_one :member_ledger, inverse_of: :member, dependent: :destroy
has_many :member_ledger_entries, through: :member_ledger
end
class Member::Ledger < ApplicationRecord
belongs_to :member, inverse_of: :member_ledger
has_many :member_ledger_entries, dependent: :destroy
end
class Member::LedgerEntry < ApplicationRecord
belongs_to :member_ledger
end

Is there a way to have two join tables associate the same two classes in a rails application?

Long time listener, first time caller. I'm trying to create two associations between the same database tables, Chatrooms and Users. What I have so far is a has_many through relationship where a Chatroom has many Users through Messages. This part works fine. What I want to do is to create a second join table that connects Chatrooms to Users, through a join table called Chatroom_players. So what I'd like is for Chatroom.first.users to get me users through the messages join table and Chatroom.first.players to get me everyone from the chatroom_players join table. The reason I want this is so that I can maintain user presence even if a user hasn't written any messages in the chat, also so that a user can leave the room but maintain his or her messages in the chat.
Here is what I have so far that does not work:
chatroom.rb:
class Chatroom < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :users, through: :messages
has_many :chatroom_players
has_many :users, through: :chatroom_players
end
message.rb:
class Message < ApplicationRecord
belongs_to :chatroom
belongs_to :user
validates :content, presence: true, length: {minimum: 2, maximum: 200}
end
chatroom_player.rb
class ChatroomPlayer < ApplicationRecord
belongs_to :chatroom
belongs_to :user
end
user.rb
class User < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :chatrooms, through: :messages
has_many :chatroom_players
has_many :chatrooms, through: :chatroom_players
end
chatroom_players migration:
class AddChatroomPlayers < ActiveRecord::Migration[5.0]
def change
create_table :chatroom_players do |t|
t.references :user, index: true, foreign_key: true, null: false
t.references :chatroom, index: true, foreign_key: true, null: false
t.boolean :creator, default: false
t.timestamps null: false
end
end
end
You need to use different names for the associations:
class Chatroom < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :users, through: :messages
has_many :chatroom_players
# this is a separate association to users through the
# chatroom_players table.
has_many :participants,
through: :chatroom_players,
source: :user, # what association on chatroom_players to use
class_name: 'User' # since it cannot be deduced automatically
end

Ruby on Rails Nested Form

I have three Models: Deal, Zipcode, DealIncludeZipcode.
Now, the association looks like below:-
Deal Model:
class Deal < ActiveRecord::Base
has_many :deal_include_zipcodes, dependent: :destroy
has_and_belongs_to_many :zipcodes, dependent: :destroy
accepts_nested_attributes_for :deal_include_zipcodes,:reject_if => :reject_include_zipcodes, allow_destroy: true
private
def reject_include_zipcodes(attributes)
if attributes[:deal_id].blank? || attributes[:zipcode_id].blank?
if attributes[:id].present?
attributes.merge!({:_destroy => 1}) && false
else
true
end
end
end
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
class DealIncludeZipcode < ActiveRecord::Base
belongs_to :deal
belongs_to :zipcode
end
Now in view I have a checkbox,on unchecking it I can select multiple zipcode to select from DealIncludeZipcode.But when I save the data it is not saving.
I have used migration for joining Zipcode and Deal Model in which my exclude zipcode functionality is working correctly.
Please provide a soloution.I have tried various method but didn't got succeed.
The whole point of has_and_belongs_to_many is that you don't have a model which joins the two parts.
class Deal < ActiveRecord::Base
has_and_belongs_to_many :zipcodes
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
Would join through a "headless" table called deals_zipcodes. If you want to have a join model you need to use has_many :through instead.
class Deal < ActiveRecord::Base
has_many :deal_zipcodes, dependent: :destroy
has_many :zipcodes, through: :deal_zipcodes
end
class DealZipcode < ActiveRecord::Base
belongs_to :deal
belongs_to :zipcode
end
class Zipcode < ActiveRecord::Base
has_many :deal_zipcodes, dependent: :destroy
has_many :deals, through: :deal_zipcodes
end
I think Max's right. So your migration should be
create_table :deals do |t|
t.string :name
...
end
create_table :zipcodes do |t|
t.string :zipcode
...
end
create_table :deals_zipcodes do |t|
t.belongs_to :deal, index: true
t.belongs_to :zipcode, index: true
end
And your models should be
class Deal < ActiveRecord::Base
has_and_belongs_to_many :zipcodes
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
You should probably take a look at the ActiveRecord guide, where you'll find more explanation.

has_many, through - undefined method `relation_delegate_class'

I have a Users class, and a UserGroup class:
class User < ActiveRecord::Base
has_many :group_memberships
has_many :users_groups, through: :group_memberships
...
class UsersGroup < ActiveRecord::Base
has_many :group_memberships
has_many :users, through: :group_memberships
... and a GroupMembership class to join them -
class GroupMembership < ActiveRecord::Base
belongs_to :users, dependent: :destroy
belongs_to :users_groups, dependent: :destroy
My migrations look like this -
class CreateUsersGroups < ActiveRecord::Migration
def change
create_table :users_groups do |t|
t.string :title
t.string :status
t.string :about
t.timestamps null: false
end
end
end
class CreateGroupMembership < ActiveRecord::Migration
def change
create_table :group_memberships do |t|
t.integer :user_id, index: true
t.integer :users_group_id, index: true
t.boolean :owner
end
end
end
So user.group_memberships is perfectly happy, but user.users_groups returns an error -
undefined method `relation_delegate_class' for Users:Module
Similarly, users_group.group_memberships is fine, but users_group.users returns exactly the same error -
undefined method `relation_delegate_class' for Users:Module
... on the users module. I've stared at this for a couple of hours, but I'm sure it's simple syntax somewhere. What's the problem?
When using belongs_to I believe you need to use a singular format:
So not belongs_to :users but belongs_to :user
class GroupMembership < ActiveRecord::Base
belongs_to :user, dependent: :destroy
belongs_to :writers_group, dependent: :destroy
I believe that since you are using 'through' you will have to use:
user.group_memberships.users_groups
This is because users does not have users_groups or vice versa.
Instead you access the users_groups through group_memberships.

Resources