Can model belong to STI child? - ruby-on-rails

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

Related

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.

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 Many_to_many association

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.

Rails: 2 user ids in a single table?

I want to make it so the following will return all received messages:
current_user.messages.received
And the following will make it so I will see all sent messages:
current_user.messages.sent
The problem I am having is that I am not sure how to specify that 2 columns in a single table both represent the foreign key for a user. How do I associate both sender_id and recipient_id to a user?
db/schema.rb
create_table "messages", force: true do |t|
t.string "notification", null: false
t.integer "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "recipient_id", null: false
end
app/models/message.rb
class Messages < ActiveRecord::Base
has_and_belongs_to_many :user
end
app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :messages, dependent: :destroy
end
In this case, each message has exactly one sender and possible many receivers.
So your models could be:
class Messages < ActiveRecord::Base
has_and_belongs_to_many :receivers, class_name: 'User'
belongs_to :sender, class_name: 'User'
end
and
class User < ActiveRecord::Base
has_many :messages, dependent: :destroy
has_and_belongs_to_many :received_messages, class_name: 'Message'
end
You need an join table for the "sent messages":
class CreateJoin < ActiveRecord::Migration
def change
create_join_table :users, :messages
end
end
Now you can access your sent and received messages:
current_user.messages # sent messages
current_userreceived_messages

uninitialized constant NameError when use has_many

I practice my RoR skills and try develop application to already created DB. It's have 4 tables: testplans, testplan_tcversions,* test_project* and nodes.
I'm code 2 models for this tables:
class TestPlan < ActiveRecord::Base
self.table_name= 'testplans'
belongs_to :test_project
has_many :test_suites, foreign_key: :testplan_id, inverse_of: :test_plan
has_one :node, foreign_key: :id, inverse_of: :test_plan
end
and
class TestSuite < ActiveRecord::Base
self.table_name='testplan_tcversions'
belongs_to :test_plan
has_one :node, foreign_key: id, inverse_of: :test_collection
end
But I get exception uninitialized constant TestPlan::TestSuite when try: #suits=TestPlan.find(4906).test_suites
I found a lot of answers that Models must singular and table must plural, but my Models names are singular, names of tables I point in self.table_name.
What I did wrong?
UPD
This my db:schema:dump
create_table "testplans", force: true do |t|
t.integer "testproject_id"
t.text "notes"
t.integer "active"
t.integer "is_open"
t.integer "is_public"
t.text "api_key"
end
create_table "testplan_tcversions", force: true do |t|
t.integer "testplan_id"
t.integer "tcversion_id"
t.integer "node_order"
t.integer "urgency"
t.integer "platform_id"
t.integer "author_id"
t.datetime "creation_ts"
end
How are your migrations set up?
If they are set up correctly, the relationship between TestSuite and TestPlan should look like this:
class TestPlan < ActiveRecord::Base
has_many :test_suites
end
class TestSuite < ActiveRecord::Base
belongs_to :test_plan
end
For this to work though, your TestSuite migration needs to have a test_plan_id column. That should look like this.
class TestSuite < ActiveRecord::Migration
belongs_to :test_plan
end
If this is set up correctly, you should then be able to call #suits=TestPlan.find(4906).test_suites.
Make sure your table names correspond to your model names. If you don't have a table named 'testplan_tcversions', the association isn't going to work.

Resources