Rails Many_to_many association - ruby-on-rails

I am new to Rails and have a problem. I have three tables: books, authors, books_authors. In my view I would like to display two columns. First should display author's name and second all books that author has written decimal. In my books_authors table are foreign keys belongs to primary keys in books and authors tables. My schema:
ActiveRecord::Schema.define(version: 20150709110928) do
create_table "authors", force: :cascade do |t|
t.string "name"
t.string "surname"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "authors_books", id: false, force: :cascade do |t|
t.integer "author_id"
t.integer "book_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "authors_books", ["author_id"], name:
"index_author_books_on_author_id"
add_index "authors_books", ["book_id"], name:
"index_author_books_on_book_id"
create_table "books", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
and models looks like:
class Author < ActiveRecord::Base
has_many :authors_books
has_many :books, through: :authors_books
end
class Book < ActiveRecord::Base
has_many :authors_books
has_many :authors, through: :authors_books
end
How could I do it?

In model authors_book, you should do this
class AuthorsBook < ActiveRecord::Base
belongs_to :author
belongs_to :book
end
Each entry in authors_books table has author_id and books_id which holds this many to many assosiation.
now when you do something this
#author.books
it will fetch all the books which that author has written.
You can easily traverse those books and display them.

I think you should modify the code a little bit. There are two ways I knew to implement many-to-many association:
has_and_belongs_to_many
has_many through
If you want use intermediate join table 'authors_books', you should use has_and_belongs_to_many, but in this case you cannot access authors_books table by model because there is not model about it.
If you want to store some data or info into the intermedidate join table, you should create the model by rails generator cmd like $ rails g model AuthorBook somedata:integer, and use has_many through. At last, delete the 'authors_books' table. The code is as follows:
class Author < ActiveRecord::Base
has_many :authorbooks
has_many :books, through: :authorbooks
end
class Book < ActiveRecord::Base
has_many :authorbooks
has_many :authors, through: :authorbooks
end
class Authorbook < ActiveRecord::Base
belongs_to :books
belongs_to :authors
end

Many-to-Many association in rails can be achieve through,
has_many:_______ ; through:______
Ex: Physicians has many patients through appointments.Also, physicians has many appointments.
Patients has many physicians through appointments. Also, here patients has many appointments.
Here the common entity is appointments. So DHH, has implemented many-to-many like this way.
physicians
has_many :appointments
has_many :patients, through: :appointments
patients
has_many :appointments
has_many :physicians, through: :appointments
appointments
belongs_to :physicians
belongs_to :patients
Hope it will helps.

Related

Can model belong to STI child?

I have a base class Place and multiple sub-classes using STI conventions. I have a separate model Post, which belongs_to one of the sub-classes of Place:
class Place < ApplicationRecord
end
class SubPlace < Place
has_many :posts, class_name: "SubPlace", foreign_key: "sub_place_id"
end
class Post < ApplicationRecord
belongs_to :sub_place, class_name: "SubPlace", foreign_key: "sub_place_id"
end
It is possible to save a new Post record using Rails console, but I get the following error when trying to find Posts for a specific SubPlace:
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column places.sub_place_id does not exist)
Is there a way to make this work, or must my associations relate to the base class only?
Added Schema:
create_table "posts", force: :cascade do |t|
t.string "title"
t.bigint "sub_place_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["sub_place_id"], name: "index_posts_on_sub_place_id"
end
create_table "places", force: :cascade do |t|
t.string "name"
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
A better way to handle assocations and STI is to just setup the assocations to the base class:
class Place < ApplicationRecord
end
class SubPlace < Place
has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
end
class AnotherKindOfPlace < Place
has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
end
class Post < ApplicationRecord
belongs_to :place
end
This keeps things nice and simple since Post does not know or care that there are different kinds of places. When you access #post.place ActiveRecord reads the places.type column and will instanciate the correct subtype.
If the base Post class also has the association you just write it as:
class Place < ApplicationRecord
has_many :posts, foreign_key: 'place_id', inverse_of: 'place'
end
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column
places.sub_place_id does not exist)
Your association in SubPlace is not valid. You should re-write that to just
class SubPlace < Place
has_many :posts
end

How to implement multiple associations between models?

I have 4 models.
UserModel
SkillModel
UserSkillModel
PreferenceSkillModel
Association I used:
UserModel
has_many :skills, through: :user_skills
has_many :user_skills
has_many :skills, :through: :preference_skills
has_many :preference_skills
SkillModel
has_many :users, through: :user_skills
has_many :users_skills
has_many :users, :through: :preference_skills
has_many :preference_skills
UserSkillModel
belongs_to :user
belongs_to :skill
PreferenceSkillModel
belongs_to :user
belongs_to :skill
Schema:
create_table "preference_skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "user_id", default: 0
t.integer "skill_id", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "user_skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "user_id"
t.integer "skill_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "name", default: ""
t.integer "skill_count", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
How can I implement this association in a correct way?
How can I implement this association in a correct way?
What is correct may depend on what you are trying to do. The following is correct, by naming separate associations for users-skills and users-preference_skills.
class User
has_many :user_skills
has_many :my_skills, through: :user_skills, class_name: 'Skill'
has_many :preference_skills
has_many :my_preference_skills, :through: :preference_skills, class_name: 'Skill'
...
end
class SkillModel
has_many :users_skills
has_many :direct_users, through: :user_skills, class_name: 'User'
has_many :preference_skills
has_many :preference_users, :through: :preference_skills, class_name: 'User'
...
end
If you want one collection with all the skills you need to use STI or polymorphism. Read the Rails Guides for more information.
I don't think there's a need for separate user_skills & preference_skills models.
You can use STI here like -
skill.rb
Skill < ApplicationRecord
end
user_skill.rb
UserSkill < Skill
end
preference_skill.rb
PreferenceSkill < Skill
end
Note: The Skill model that I've used is different from what you are assuming.
You might want to rename your current Skills model to more explainatory name like SkillDetail.
After this you can have your associations like -
class User < AR
has_many :skills
has_many :skill_details, through: :skills
end
class Skill < AR
belongs_to :user
belongs_to :skill_detail
end
class SkillDetail < AR
has_many :skills
has_many :users, through: :skills
end
P.S. Do some research on Single Table Inheritance (STI)
Hope this helps.
Also note that you got a downvote because this is a very vague question & you are directly asking to implement a business logic.

Rails self-referential has_many association

Using Ruby on Rails, I think I need to create a self-referential has_many association to model words in Chinese.
Background:
Each word is a composite of multiple component words.
For example, if I have three words, 'ni', 'hao', and 'nihao', I want to be able to do:
nihao.components = ['ni', 'hao']
and
'ni'.composites = ['nihao']
'hao'.composites =['nihao']
I don't believe this should be a hierarchical association (I've seen several gems... ) because a word doesn't have 1 or 2 "parents", it has 0, 1, or hundreds of "composites". Likewise a word has 0, 1, or several "components".
I've tried:
class Word < ActiveRecord::Base
has_many :relationships
has_many :components, through: :relationships, foreign_key: :component_id
has_many :composites, through: :relationships, foreign_key: :composite_id
end
class Relationship < ActiveRecord::Base
belongs_to :component, class_name: "Word"
belongs_to :composite, class_name: "Word"
end
This isn't quite correct as I am unable to add components:
nihao.components << ni
(0.2ms) BEGIN
(0.2ms) ROLLBACK
ActiveModel::UnknownAttributeError: unknown attribute 'word_id' for Relationship.
from (irb):5
Database schema:
create_table "relationships", force: :cascade do |t|
t.integer "component_id"
t.integer "composite_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "words", force: :cascade do |t|
t.string "characters"
t.string "pinyin"
t.string "opinyin"
t.string "tpinyin"
t.string "english"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Try this, you were not associating your models properly for this kind of use case.
class Word < ActiveRecord::Base
has_many :component_relationships, class_name: 'Relationship', foreign_key: :composite_id
has_many :composite_relationships, class_name: 'Relationship', foreign_key: :component_id
has_many :components, through: :component_relationships
has_many :composites, through: :composite_relationships
end
class Relationship < ActiveRecord::Base
belongs_to :component, class_name: "Word", foreign_key: :component_id
belongs_to :composite, class_name: "Word", foreign_key: :composite_id
end
I have not tried this, but this should work.

how to update schema after adding "accepts_nested_attributes_for" to a model

here is my model
class Lineup < ApplicationRecord
has_many :artists
accepts_nested_attributes_for :artists
belongs_to :event
end
class Artist < ApplicationRecord
has_many :events, :through => :lineups
has_many :lineups
end
when running this in the console
Lineup.new(artists_attributes: [{artist_id: 1}, {artist_id: 2}])
the error is ActiveModel::UnknownAttributeError: unknown attribute 'artists_attributes' for Lineup. Obviously, I can't just drop something like that into a model and expect any changes to be from that alone. Do I need to run a migration for this? If so, what needs to be in it?
schema:
create_table "artists", force: :cascade do |t|
t.string "name"
t.text "bio"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "lineup_id"
t.integer "event_id"
end
add_index "artists", ["event_id"], name: "index_artists_on_event_id", using: :btree
add_index "artists", ["lineup_id"], name: "index_artists_on_lineup_id", using: :btree
create_table "lineups", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "artist_id"
t.integer "event_id"
end
add_index "lineups", ["artist_id"], name: "index_lineups_on_artist_id", using: :btree
add_index "lineups", ["event_id"], name: "index_lineups_on_event_id", using: :btree
I would set it up like this
schema
table Lineup
...
table Artist
...
table LineupArtists
lineup_id: Integer
artist_id: Integer
models
class Artist < ApplicationRecord
has_many :lineup_artists, dependent: :destroy
has_many :lineups, through: :lineup_artists
end
class LineupArtist < ApplicationRecord
belongs_to :lineup
belongs_to :artist
accepts_nested_attributes_for :lineup
end
class Lineup < ApplicationRecord
has_many :lineup_artists, inverse_of: :lineup, dependent: :destroy
has_many :artists, through: lineup_artists
accepts_nested_attributes_for :lineup_artists
end
The way you currently have it (with Artists having a lineup_id and Lineups having an artist_id) each model can only have one of the other (i.e. an Artist can have one lineup and vise versa). A join table gives the ability to have a many-to-many relationship.
Setting up nested attributes is a little trickier with a many-to-many but I believe what I posted should cover it. If not got some of the weirder bits from this post (https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through). Hope this helps.
Try to replace line:
has_many :artists
With this one:
has_many :artists, foreign_key: "lineup_id"

Rails model association for a soccer match. Match has two Country

Any suggestions on how to set up this relationship? A Match has two Country on each side, right?
However, Rails Admin is complaining that this is not the right way.
create_table "countries", force: true do |t|
t.string "name"
t.string "flag"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "matches", force: true do |t|
t.integer "country_a"
t.integer "country_b"
t.integer "score_country_a"
t.integer "score_country_b"
end
class Country < ActiveRecord::Base
belongs_to :matches
end
class Match < ActiveRecord::Base
belongs_to :country_side_a, foreign_key: :country_a
belongs_to :country_side_b, foreign_key: :country_b
end
How can I configure these relationships properly?
Here's how I ultimately set up the relations:
class Country < ActiveRecord::Base
has_many :matches_a, foreign_key: :country_a, class_name: "Match"
has_many :matches_b, foreign_key: :country_b, class_name: "Match"
end
class Match < ActiveRecord::Base
belongs_to :country_side_a, foreign_key: :country_a, class_name: "Country"
belongs_to :country_side_b, foreign_key: :country_b, class_name: "Country"
end
Country needs to have a has_one :matches, if you want a one to one mapping, or has_many :matches if you want a many to many relationship.
Have a read about how to set up a one to one/many relationship here:
http://guides.rubyonrails.org/association_basics.html#the-belongs-to-association

Resources