How to make has_many work? - ruby-on-rails

Models:
class Game < ActiveRecord::Base
belongs_to :manufacturer
end
.
class Manufacturer < ActiveRecord::Base
has_many :games
end
Migrations:
class CreateManufacturers < ActiveRecord::Migration
def change
create_table :manufacturers do |t|
t.string :name
t.timestamps null: false
end
end
end
.
class CreateGames < ActiveRecord::Migration
def change
create_table :games do |t|
t.string :name
t.references :manufacturer, index: true, foreign_key: true
t.timestamps null: false
end
end
end
When I enter m = Game.find(2).manufacturer this works and gives me the manufacturer name. But when I give g = Manufacturer.find(1).games console throws many errors. Why does not the has_many work?
Or
How do I get all the games developed by the 1st manufacturer?
Error shown:
NoMethodError: undefined method `games' for #<Manufacturer:0x007f996dc75368>

Related

rails - can't save data in associated models in RoR

I have 2 Models with association has_many along with cascade property between them.
class ServicesBrandDetail < ApplicationRecord
has_many :services_brands, foreign_key: "brand_id", dependent: :delete_all
end
class ServicesBrand < ApplicationRecord
belongs_to :services_brand_details, foreign_key: "brand_id",
end
Migration for both files
class CreateServicesBrandDetails < ActiveRecord::Migration[6.1]
def change
create_table :services_brand_details do |t|
t.string :brand
t.string :mail_list
t.string :cc_list
t.timestamps
end
end
end
class CreateServicesBrands < ActiveRecord::Migration[6.1]
def change
create_table :services_brands do |t|
t.string :warehouse
t.references :brand, null: false, foreign_key: {to_table: :services_brand_details}
t.timestamps
end
end
end
Now I was able to create and save data in from ServicesBrandDetails model. but the Problem is when i create record from ServiceBrand It created record perfectly but i was not able to store data in DB.
record = ServicesBrandDetail.create(:brand => "a", :mail_list => 'abc#mail.com', :cc_list => 'def#mail.com')
record.save
Record successfully stored in DB.
child = record.services_brands.new(:warehouse => "in") <-- record was created successfully.
child.save
it give me error
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/activerecord-6.1.5/lib/active_record/inheritance.rb:237:in `compute_type': uninitialized constant ServicesBrand::ServicesBrandDetails (NameError)
Please follow proper Naming convention
This article might help - https://www.bigbinary.com/learn-rubyonrails-book/summarizing-rails-naming-conventions
In ServiceBrand Model
class ServiceBrand < ApplicationRecord
belongs_to :brand, class_name: 'ServiceBrandDetail'
end
belongs_to should be foreign key name i.e brand in your case
You can delete existing models and tables from your codebase and try below one. (I've tested)
class ServiceBrandDetail < ApplicationRecord
has_many :service_brands, foreign_key: :brand_id, dependent: :delete_all
end
class ServiceBrand < ApplicationRecord
belongs_to :brand, class_name: 'ServiceBrandDetail'
end
Migration for both files
class CreateServiceBrandDetails < ActiveRecord::Migration[6.1]
def change
create_table :service_brand_details do |t|
t.string :brand
t.string :mail_list
t.string :cc_list
t.timestamps
end
end
end
class CreateServiceBrands < ActiveRecord::Migration[6.1]
def change
create_table :service_brands do |t|
t.string :warehouse
t.references :brand, null: false, foreign_key: {to_table: :service_brand_details}
t.timestamps
end
end
end
Then try to create model objects which you tried in your question. It will work 👍🏽
In your model ServicesBrand you have to use singular association name for belongs_to
Change this belongs_to :services_brand_details to this belongs_to :services_brand_detail
class ServicesBrand < ApplicationRecord
belongs_to :services_brand_detail, foreign_key: "brand_id"
end

ActiveModel::MissingAttributeError: can't write unknown attribute `team_id`

I find this issue when I try to save the a team, how can I solve it? I am trying to deal with these associations for so long, if you guys do other issues, just let me know, please (this association has been a nightmare).
Here are the models
class Field < ApplicationRecord
end
class Game < ApplicationRecord
belongs_to :field
belongs_to :organiser
has_one :team
has_many :players, through: :team
end
class Organiser < ApplicationRecord
has_many :games
end
class Player < ApplicationRecord
has_many :teams
has_many :games, through: :teams
end
class Team < ApplicationRecord
has_many :players
belongs_to :game
end
Here are the migrations
class CreateOrganisers < ActiveRecord::Migration[5.2]
def change
create_table :organisers do |t|
t.string :name
t.string :email
t.integer :age
t.timestamps
end
end
end
class CreatePlayers < ActiveRecord::Migration[5.2]
def change
create_table :players do |t|
t.string :name
t.integer :age
t.string :address
t.timestamps
end
end
end
class CreatePlayers < ActiveRecord::Migration[5.2]
def change
create_table :players do |t|
t.string :name
t.integer :age
t.string :address
t.timestamps
end
end
end
class CreateFields < ActiveRecord::Migration[5.2]
def change
create_table :fields do |t|
t.string :location
t.string :transports
t.timestamps
end
end
end
class CreateGames < ActiveRecord::Migration[5.2]
def change
create_table :games do |t|
t.references :field, foreign_key: true
t.references :organiser, foreign_key: true
t.integer :size
t.timestamps
end
end
end
class CreateTeams < ActiveRecord::Migration[5.2]
def change
create_table :teams do |t|
t.references :player, foreign_key: true
t.references :game, foreign_key: true
t.timestamps
end
end
end
class AddTeamToGames < ActiveRecord::Migration[5.2]
def change
add_column :games, :team, :reference
end
end
The idea is to make sure that each game will have a team of certain people. I want to access the people through game.team.player
Your migrations doesn't have any kind of references to connect these tables.
For example Game has_one :team is not going to create this references for you, to call game.team needs a column called game_id in teams table.
To add the reference you can create a migration for it:
rails g migration AddGameToTeams game:references
This will create that migration file may look like this:
class AddGameToTeams < ActiveRecord::Migration
def change
add_reference :teams, :game, index: true
end
end
Depending in the version you running rails this migration file may differ a little with adding an extra line regarding foreign_key.
Run the generated migration with rails db:migrate and it should work.
You need to apply the same concept to the rest of your tables.
Hope this helps.

Category has_many videos, video belongs_to_many categories

I'm doing like a little video site like 9gagtv style
and videos in the site has a category so the user can find all the Tech videos for example
but some videos belongs_to_many categories like when a video is about tech but also is funny so it will show up in both categories videos and i'm not sure how to do this?
would it require more than one t.references in videos table ? how would the relationship go?
category.rb
class Category < ApplicationRecord
has_many :videos
end
video.rb
class Video < ApplicationRecord
belongs_to :category
end
category migrations
class CreateCategories < ActiveRecord::Migration[5.0]
def change
create_table :categories do |t|
t.string :title
t.timestamps
end
end
end
video migrations
class CreateVideos < ActiveRecord::Migration[5.0]
def change
create_table :videos do |t|
t.string :url
t.string :title
t.text :description
t.integer :duration
t.references :category, foreign_key: true
t.timestamps
end
end
end
You can use has_and_belongs_to_many
to create many-to-many association through the third table.
Models:
class Category < ApplicationRecord
has_and_belongs_to_many :videos
end
class Video < ApplicationRecord
has_and_belongs_to_many :categories
end
Migrations:
class CreateCategories < ActiveRecord::Migration[5.0]
def change
create_table :categories do |t|
t.string :title
t.timestamps
end
end
end
class CreateVideos < ActiveRecord::Migration[5.0]
def change
create_table :videos do |t|
t.string :url
t.string :title
t.text :description
t.integer :duration
t.timestamps
end
end
end
class CreateCategoriesVideos < ActiveRecord::Migration[5.0]
def change
create_table :categories_videos do |t|
t.references :category, index: true
t.references :video, index: true
end
end
end
I think what you're looking for is has_and_belongs_to_many relation (see more)
It should look something like that
Category
class Category < ApplicationRecord
has_and_belongs_to_many:videos
end
Video
class Video < ApplicationRecord
belongs_to :category
end
And migration
class CreateCategoriesVideosJoinTable < ActiveRecord::Migration
def change
create_table :categories_videos, id: false do |t|
t.integer :category_id
t.integer :video_id
end
end
end

Callbacks - Set field on model

I am trying the following -
I have Models: Tales, Books, Keywords
class Tale < ActiveRecord::Base
has_many :tale_culture_joins
has_many :cultures, through: :tale_culture_joins
has_many :tale_purpose_joins
has_many :purposes, through: :tale_purpose_joins
has_many :tale_book_joins
has_many :books, through: :tale_book_joins
has_many :tale_keyword_joins
has_many :keywords, through: :tale_keyword_joins
class Book < ActiveRecord::Base
has_many :tale_book_joins
has_many :tales, through: :tale_book_joins
end
class TaleBookJoin < ActiveRecord::Base
belongs_to :tale
belongs_to :book
end
class Keyword < ActiveRecord::Base
has_many :tale_keyword_joins
has_many :tales, through: :tale_keyword_joins
end
class TaleKeywordJoin < ActiveRecord::Base
belongs_to :tale
belongs_to :keyword
end
These are the migrations
class CreateTales < ActiveRecord::Migration
def change
create_table :tales do |t|
t.text :name, null: false, unique: true
t.boolean :exists, default: nil
t.timestamps null: false
end
end
end
class CreateBooks < ActiveRecord::Migration
def change
create_table :books do |t|
t.text :name, null: false, unique: true
t.boolean :exists, default: nil
t.timestamps null: false
end
end
end
class CreateKeywords < ActiveRecord::Migration
def change
create_table :keywords do |t|
t.text :name, null: false, unique: true
t.boolean :exists, default: nil
t.timestamps null: false
end
end
end
What I want to happen is that everytime i delete a join between (Tale, Book) or (Tale,Keyword) through following method
tale_instance_object.book_ids = []
It should go and check if the books for whom the relations have been broken have any other tale relations. If not then set :exists in Book object instance to false.
I am able to do this through controller code.
Wondering how CallBacks or ActiveModel can be used
Join classes should only really be used when the relation is an object on its own.
Consider these cases:
doctors -> appointments <- patients
years -> days <- hours
In these cases the relation object has data of its own (appointments.time, days.weekday) and logic. Otherwise you are just wasting memory since a object has to instantiated for every relation. Use has_and_belongs_to instead.
class Tale < ActiveRecord::Base
# has_and_belongs_to_many :cultures
# has_and_belongs_to_many :purposes
has_and_belongs_to_many :books
has_and_belongs_to_many :keywords
after_destroy :update_books!
end
class Book
has_and_belongs_to_many :tales
def check_status!
self.update_attribute(status: :inactive) unless books.any
end
end
class Keyword
has_and_belongs_to_many :tales
end
Also exists is a really bad naming choice for a model field since it collides with Rails built in exists? methods. This will have unexpected consequences.
A better alternative would be to use an integer combined with an enum.
class Book
# ...
enum :status [:inactive, :active] # defaults to inactive
end
class CreateBooks < ActiveRecord::Migration
def change
create_table :books do |t|
t.text :name, null: false, unique: true
t.integer :status, default: 0
t.timestamps null: false
end
end
This will also add the methods book.inactive? and book.active?.
You could add a callback to Tale which tells Book to update when a tale is updated but this code really stinks since Tale now is responsible for maintaining state of Book.
class Tale < ActiveRecord::Base
# ...
after_destroy :update_books!
def update_books!
self.books.each { |b| b.check_status! }
end
end
class Book
has_and_belongs_to_many :tales
def check_status!
self.update_attribute(status: :inactive) unless tales.any?
end
end
A better alternative to add a callback on Books or to do it in the controller when destroying a tale:
class Book
has_and_belongs_to_many :tales
before_save :check_for_tales!, if: -> { self.tales_changed? }
def check_for_tales!
self.status = :inactive unless self.tales.any?
end
end

Activerecord many-to-many relationship not working

I'm working on a twitter clone and have been having trouble with some of my associations. I want a user to have many tweets. My tweets will have a many-to-many relationship with hashtags, so in theory I want to be able to call User.find(1).hashtags and have a list of the user's hashtags (edit: solved this issue). I also am having trouble pushing hashtags into a specific tweet.
Here are my models:
class User < ActiveRecord::Base
has_many :tweets
end
class Tweet < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :hashtags
end
class Hashtag < ActiveRecord::Base
has_and_belongs_to_many :tweets
end
Here are my migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :username
t.string :email
t.string :password
t.timestamps
end
end
end
class CreateTweets < ActiveRecord::Migration
def change
create_table :tweets do |t|
t.string :content
t.belongs_to :user
t.timestamps
end
end
end
class CreateHashtags < ActiveRecord::Migration
def change
create_table :hashtags do |t|
t.string :tag
t.timestamps
end
end
end
class CreateHashtagsTweets < ActiveRecord::Migration
def change
create_table :hashtags_tweets, id: false do |t|
t.references :hashtag
t.references :tweet
end
end
end
Any help would be greatly appreciated! Thanks!

Resources