I have a 2 models Game & Theme and they have a has_and_belongs_to_many association. I have tried many solutions to prevent duplicate records in the games_themes table, but no solutions work. The problem is, games_themes is a table, but it is not a model, so I can't figure out a way to run validations on it effectively.
Heres a solution I tried
class Theme < ActiveRecord::Base
has_and_belongs_to_many :games, :uniq => true
end
class Game < ActiveRecord::Base
has_and_belongs_to_many :themes, :uniq => true
end
You should use database-level validation:
#new_migration
add_index :games_themes, [:game_id, :theme_id], :unique => true
HABTM
This will prevent you saving any duplicate data in the database. Takes the burden off Rails & ensures you only have game or theme. The problem is because HABTM doesn't have a model, there's no validation you can perform in Rails, meaning you need to make it db-level
As mentioned in the comments, this means you'll have to handle the exceptions raised from the db like this:
#app/controllers/games_controller.rb
def create
#creation stuff here
if #game.save
#successful save
else
#capture errors
end
end
Use:
validates_uniqueness_of :theme_id, :scope => :game_id
As follows:
class Theme < ActiveRecord::Base
has_many :games, through: :games_themes
end
class Game < ActiveRecord::Base
has_many :themes, through: :games_themes
end
class GamesThemes < ActiveRecord::Base
belongs_to :game
belongs_to :theme
validates_uniqueness_of :theme_id, :scope => :game_id
end
To run validations on join table you should use has_many :through association instead.
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Creating new Model GameTheme for validation purpose is not a good idea. We can validate itself in migration.
Theme Model:
class Theme < ActiveRecord::Base
has_and_belongs_to_many :games,
:association_foreign_key => 'theme_id',
:class_name => 'Theme',
:join_table => 'games_themes'
end
Game Model:
class Theme < ActiveRecord::Base
has_and_belongs_to_many :games,
:association_foreign_key => 'game_id',
:class_name => 'Game',
:join_table => 'games_themes'
end
games_themes migration:
You can add uniqueness to join table, Have a look here for more detail.
class GamesThemesTable < ActiveRecord::Migration
def self.up
create_table :games_themes, :id => false do |t|
t.references :game
t.references :theme
end
add_index :games_themes, [:theme_id, :game_id], :unique => true
end
def self.down
drop_table :games_themes
end
end
Related
I am trying to connect two classes (conversation and user) by a many-to-many relationship in Ruby on Rails. I set them both up and added a connection table called conversations_custom_users to connect them and it was working. Once we needed our User model to inherit from another User model, setting conversations in a user object was failing and looking for a connection table with the parent class.
My classes and the conversation migration looks like below (I haven't modified the User migration for the many-to-many relationship):
class CustomUser < Spree::User
serialize :resources, Array
has_and_belongs_to_many :conversations, :foreign_key => :conversation_ids, class_name: 'Conversation'
end
class Conversation < ApplicationRecord
has_and_belongs_to_many :receiver, :foreign_key => :receiver_id, class_name: 'CustomUser'
end
class CreateConversations < ActiveRecord::Migration[6.1]
def change
create_table :conversations do |t|
t.timestamps
end
create_table :conversations_custom_users, id: false do |t|
t.belongs_to :conversation, foreign_key: 'conversation_id', index: true
t.belongs_to :custom_user, foreign_key: 'receiver_id', index: true
end
end
end
I think I shouldn't need to add another table called conversations_spree_users, but I also tried adding one. It didn't solve the problem since then Rails was looking for a spree_user_id field. I also tried adding the spree_user_id field to the conversations_spree_users table, but it wouldn't migrate because it was a duplicate column name!
I think I'm missing something about many-to-many relations or inheritance or both in Ruby. If someone can help with this issue I'd really appreciate it.
you could use polymorphic associations to build many-to-many association, the benefit of this approach is that you can use only one join-table for all user's hierarchy inheritance.
class CreateConversationals < ActiveRecord::Migration[6.1]
def change
create_table :conversationals do |t|
# ...
t.references :contributor, polymorphic: true, null: false
t.integer :conversation_id
t.timestamps
end
end
end
class Conversational < ApplicationRecord
belongs_to :contributor, polymorphic: true
belongs_to :conversation
end
class Conversation < ApplicationRecord
has_many :conversationals, :foreign_key => :conversation_id
has_many :custom_users, :through => :conversationals, :source => :contributor, :source_type => 'CustomUser'
has_many :other_users, :through => :conversationals, :source => :contributor, :source_type => 'OtherUser'
end
class CustomUser < Spree::User
has_many :conversationals, as: :contributor
has_many :conversations, :through => :conversationals, :as => :contributor
end
# i assume you use STI
class OtherUser < CustomUser
end
then
user1 = CustomUser.create(...)
user2 = OtherUser.create(...)
conversation = Conversation.create(...)
conversational1 = Conversational.create(..., conversation_id: conversation.id, contributor: user1)
conversation1 = Conversational.create(..., conversation_id: conversation.id, contributor: user2)
# many-to-many
user1.conversations
user2.conversations
conversation.custom_users
conversation.other_users
I have a model named Comics. My overall goal is to allow every comic to have optional variants(has_many comics) and a single variant_of(belongs_to comic). When I add a variant_of, I expect the inverse variants field to also resemble the same relationship.
I started by creating a variant_of migration:
class AddVariantOfToComics < ActiveRecord::Migration
def self.up
add_column :comics, :variant_of, :integer
end
def self.down
remove_column :comics, :variant_of, :integer
end
end
It worked splendidly. Then I attempted to make a variants index, which is the aspect I am having trouble with:
class AddVariantsToComics < ActiveRecord::Migration
def change
add_index :comics, ['variant_of'], :name => 'variants'
end
end
The comics model:
class Comic < ActiveRecord::Base
has_many :variants, :class_name => "Comic", :foreign_key => 'variants',
belongs_to :variant_of, :class_name => "Comic", :foreign_key => 'variant_of'
...
end
Can anyone tell me how to relate these fields? Is index the right way to do that or is my naming getting in the way?
The foreign key of the has_many association is supposed to be variant_of. Like:
has_many :variants, :class_name => "Comic", :foreign_key => 'variant_of'
And it is not supposed to have a comma after it. In your example it is: :foreign_key => 'variants', which is not a valid syntax.
I am trying to add a "following" like functionality to my site but I am having trouble finding the right way to use a polymorphic association. A user needs to be able to follow 3 different classes, these 3 classes do not follow the user back. I have created a user following user in the past but this is proving to be more difficult.
My Migration was
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.integer :follower_id
t.integer :relations_id
t.string :relations_type
t.timestamps
end
end
end
My Relationship model is
class Relationship < ActiveRecord::Base
attr_accessible :relations_id
belongs_to :relations, :polymorphic => true
has_many :followers, :class_name => "User"
end
In my User model
has_many :relationships, :foreign_key => "supporter_id", :dependent => :destroy
and in the other 3 models
has_many :relationships, :as => :relations
Am I missing something with setting up this association?
You basically have it right, except for a few minor errors:
attr_accessible :relations_id is redundant. Remove it from your Relationship model.
Both Relationship and User models call has_many to associate with each other. Relationship should call belongs_to because it contains the foreign key.
In your User model, set :foreign_key => "follower_id".
Here is how I would do it.
Have a Follow middle class with polymorphic association on the followable content side and has_many on the follower user side (user has many follows).
First, create a follows table:
class CreateFollows < ActiveRecord::Migration
def change
create_table :follows do |t|
t.integer :follower_id
t.references :followable, :polymorphic => true
t.timestamps
end
end
end
Replace Relationship model with a Follow model:
class Follow < ActiveRecord::Base
belongs_to :followable, :polymorphic => true
belongs_to :followers, :class_name => "User"
end
Include in User model:
has_many :follows, :foreign_key => :follower_id
Include in your three followable classes:
has_many :follows, :as => :followable
You can now do this:
TheContent.follows # => [Follow,...] # Useful for counting "N followers"
User.follows # => [Follow,...]
Follow.follower # => User
Follow.followable # => TheContent
I just created a counter_cache field and the controller looks like this.
#users = User.where(:sex => 2).order('received_likes_count')
The association in User.rb is
has_many :received_likes, :through => :attachments, :source => :likes, :dependent => :destroy
Problem is that counter_cache is declared in the belong_to of Like.rb and I don't know how to tell it that is for the has_many :through association.
belongs_to :user, :counter_cache => :received_likes
You have previous
class Product
has_and_belongs_to_many :categories
end
class Category
has_and_belongs_to_many :products
end
and migration
class CreateCategoriesProducts < ActiveRecord::Migration
def change
create_table :categories_products, id: false do |t|
t.references :category
t.references :product
end
add_index :categories_products, [:category_id, :product_id]
end
end
now change all to
class Product
has_many :categories_products, dependent: :destroy
has_many :categories, through: :categories_products
end
class Category
has_many :categories_products, dependent: :destroy
has_many :products, through: :categories_products
end
and new one
class CategoriesProduct < ActiveRecord::Base
# this model uses table "categories_products" as it is
# column products_count is in the table "categories"
belongs_to :category, counter_cache: :products_count
belongs_to :product
end
According to this post (from last month) and this post (from 2008), it doesn't seem to be possible. However, the latter post does have code for a workaround (copy/paste'd from that link for your convenience, credit goes to DEfusion in the second link)
class C < ActiveRecord::Base
belongs_to :B
after_create :increment_A_counter_cache
after_destroy :decrement_A_counter_cache
private
def increment_A_counter_cache
A.increment_counter( 'c_count', self.B.A.id )
end
def decrement_A_counter_cache
A.decrement_counter( 'c_count', self.B.A.id )
end
end
(This is for a scheme where C belongs_to B, B belongs_to A, A has_many C :through => B
This basically does the same thing:
after_save :cache_post_count_on_tags
def cache_post_count_on_tags
tags.each {|t| tag.update_attribute(:posts_count, t.posts.size)}
end
And you need a posts_count column on tags, or whatever associations you have.
I have the following two models:
class Project < ActiveRecord::Base
has_one :start_date, :class_name => 'KeyDate', :dependent => :destroy
has_one :end_date, :class_name => 'KeyDate', :dependent => :destroy
and
class KeyDate < ActiveRecord::Base
belongs_to :project
Given a certain key date from the database related to a project:
#key_date = KeyDate.find(:first)
is there a way to introspect the relationship to check if the #key_date is related to the project as start_date or as end_date?
A nice way would be to use single table inheritance for the KeyDate class
class KeyDate < ActiveRecord::Base
belongs_to :project
end
class StartDate < KeyDate
end
class EndDate < KeyDate
end
class Project < ActiveRecord::Base
has_one :start_date, :dependent => :destroy
has_one :end_date, :dependent => :destroy
end
class CreateKeyDatesMigration < ActiveRecord::Migration
def up
create_table :key_dates do |t|
t.date :date
t.string :type #this is the magic column that activates single table inheritance
t.references :project
end
end
…
end
this lets you do
#key_date = KeyDate.find(:first)
#key_date.type # => "StartDate"
One clean way to do what you want is to create STI:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
See one example I gave here:
Rails devise add fields to registration form when having STI
Just thinking aloud...
class KeyDate < ActiveRecord::Base
belongs_to :project
def start_date?
project.start_date == self
end
def end_date?
project.start_date == self
end
date_type
[:start_date, :end_date].find {|sym| send("#{sym}?") }
end
end
To be honest I can't see why you'd ever need this. Surely you're always going to have a handle on a project anyway?