has_and_belongs_to_many UndefinedTable: ERROR - ruby-on-rails

I have two models, Clinician and Patient. A clinician has_many: patients and a patient belongs_to :clinician. A join table, shared_patients is meant to store additional associations between patients and clinicians as a patient can be shared by many other clinicians besides the one it belongs_to. This is done using a has_and_belongs_to_many relationship.
See models:
class Clinician < ActiveRecord::Base
has_many :patients
has_and_belongs_to_many :shared_patients, join_table: 'shared_patients', class_name: 'Patient'
end
class Patient < ActiveRecord::Base
belongs_to :clinician
has_and_belongs_to_many :shared_clinicians, join_table: 'shared_patients', class_name: 'Clinician'
end
This is how my tables are set out in the db schema:
create_table "clinicians", force: true do |t|
t.string "first_name"
t.string "last_name"
t.integer "user_id"
end
create_table "patients", force: true do |t|
t.integer "clinician_id"
t.string "first_name"
t.string "last_name"
t.integer "user_id"
end
create_table "shared_patients", id: false, force: true do |t|
t.integer "clinician_id"
t.integer "patient_id"
end
Using these I would like to show the list of clinicians that a patient is shared with.
Right now I am getting an error:
PG::UndefinedTable: ERROR: relation "shared_patients" does not exist
LINE 1: INSERT INTO "shared_patients" ("clinician_id", "id", "patien...
If I try and create a relationship in console:
#shared = SharedPatient.new("id"=>1, "clinician_id"=>2526, "patient_id"=>1307)
=> #1, "clinician_id"=>2526, "patient_id"=>1307}>
#shared.save
Any advice on solving this error or on structuring the models to get the associations I want would be great. Thanks

When you have a has_and_belongs_to_many then you cannot have a class for the join table, ie. you can't have SharedPatient and you can't try using it as you've done.

Related

How to set an instance of a model as an attribute of another model in Rails?

I am fairly new to Rails and working on an app that will allow a user to make a List containing their top 5 Items of a certain category. The main issue I'm having is how to keep track of the List order (which should be allowed to change and will be different for each User)?
My Items can belong to many Lists and my Lists should have many Items so, as of now, I am using a has_and_belongs_to_many association for both my Item and List models.
My idea to keep track of the list order right now is to have my #list have 5 attributes: one for each ranking on the list (ie. :first, :second, :third, :fourth, :fifth) and I am attempting to associate the #item instance to the #list attribute (ie. #list.first = #item1, #list.second = #item2 , etc...). Right now I am saving the #list attribute to the #item ID (#list.first = 1), but I would prefer to be able to call the method .first or .second etc and have that point directly at the specific Item instance.
Here is my current schema for lists, items, and the join table list_nominations required for the has_and_belongs_to_many association-which I'm pretty sure I am not utilizing correctly (the :points attribute in items will be a way of keeping track of popularity of an item:
create_table "lists", force: :cascade do |t|
t.integer "user_id"
t.integer "category_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "first"
t.string "second"
t.string "third"
t.string "fourth"
t.string "fifth"
end
create_table "items", force: :cascade do |t|
t.string "name"
t.integer "category_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "points", default: 0
end
and here is the code currently in my List and Item models:
class List < ApplicationRecord
belongs_to :user
belongs_to :category
has_and_belongs_to_many :items
end
class Item < ApplicationRecord
belongs_to :category
has_and_belongs_to_many :lists
end
Is there a way to do this or any suggestions on a better way to keep track of the List order without creating multiple instances of the same Item?
I'm afraid your tables don't fit any known approach, you can achieve what you want but this is not a perfect nor a recommended solution, you could specify the primary key on many has_one associations inside lists but in items it's not very possible to have all lists in one association but you can have an instance method which query lists and returns the matched ones
the hacky solution:
class List < ApplicationRecord
belongs_to :user
belongs_to :category
has_one :first_item, primary_key: :first, class_name: "Item"
has_one :second_item, primary_key: :second, class_name: "Item"
has_one :third_item, primary_key: :third, class_name: "Item"
has_one :fourth_item, primary_key: :fourth, class_name: "Item"
has_one :fifth_item, primary_key: :fifth, class_name: "Item"
end
class Item < ApplicationRecord
belongs_to :category
def lists
List.where(
"first = ? OR second = ? OR third = ? OR fourth = ? OR fifth = ?", self.id, self.id, self.id, self.id, self.id
)
end
end
you can read about how to create a many-to-many relationship via has_and_belongs_to_many associations here: https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association (your tables will need a field to properly point to each other)
What I recommend doing is following a many-to-many through relationship guide (mono-transitive association) :
you will need 1 extra table because you want to track the order(first,second, etc)
DB:
create_table "lists", force: :cascade do |t|
.. all your other fields without first,second, etc..
end
create_table "items", force: :cascade do |t|
.. all your other fields
end
create_table "lists_items", force: :cascade do |t|
t.integer "list_id"
t.integer "item_id"
t.integer "rank" there is where you will store your order (first, second ..) bas as an integer
end
Models:
class ListsItem < ApplicationRecord
belongs_to :list
belongs_to :item
end
class List < ApplicationRecord
belongs_to :user
belongs_to :category
has_many :lists_items, -> { order(:rank) }, limit: 5
has_many :items, through: :lists_items
end
class Item < ApplicationRecord
belongs_to :category
has_many :lists_items
has_many :lists, through: :lists_items
end
you can read more about many-to-many via has_many through here https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
and the difference between the 2 approaches here https://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many

Rails complex association

I have this models:
class Product < ActiveRecord::Base
has_many :feature_products, -> { where("taxonomy_slug = feature_taxonomy_slug") }
has_many :features, through: :feature_products, class_name: "Feature", source: :feature
end
class Feature < ActiveRecord::Base
has_many :feature_products, -> (object){ where(" feature_taxonomy_slug = ?", object.taxonomy_slug) }, primary_key: :external_id
has_many :products, through: :feature_products
end
class FeatureProduct < ActiveRecord::Base
belongs_to :product
belongs_to :feature, primary_key: :external_id
end
and the tables are like this:
create_table "feature_products", force: :cascade do |t|
t.integer "product_id"
t.integer "feature_id"
t.string "feature_taxonomy_slug"
end
create_table "features", force: :cascade do |t|
t.integer "external_id"
t.string "name"
t.string "taxonomy_slug"
end
create_table "products", force: :cascade do |t|
t.integer "company_id"
end
I want to be able to create feature products association like this:
feature = Feature.create(external_id: 1234, name: 'WS', taxonomy_slug: 'prop')
Product.create(name: 'XXX', features: [feature])
The problem is the table feature_products, it stores the ids but it doesn't store the feature_taxonomy_slug from the feature. Is there any way to store it?
I think if you want to save the slug in the FeatureProduct model then you are going to have to create one explicitly, i.e.
FeatureProduct.create(product: product, feature: feature, feature_taxonomy_slug: feature.taxonomy_slug)
I do not understand why you want to save the slug in the FeatureProduct model.
Is there a reason why you do not want to access it through the product.features association?

rails admin has_many through relationship leading to errors

I am using rails_admin gem for admin interface.
I have a has_many through relationship which doesn't seem to work with rails admin.
class Company < ActiveRecord::Base
has_many :talent_infos, class_name: 'CompanyTalentInfo'
has_many :talents, through: :talent_infos
end
class CompanyTalentInfo < ActiveRecord::Base
belongs_to :company
belongs_to :talent
end
class Talent < ActiveRecord::Base
has_many :talent_infos, class_name: 'CompanyTalentInfo'
has_many :companies, through: :talent_infos
end
I get error every time I try to create a new company and my guess is that its the first time when rails_admin tries to check the relationships and it doesn't accept my current associations.
The error I get is this file gems/rails_admin-0.7.0/app/views/rails_admin/main/_form_filtering_multiselect.html.haml:21
21 controller.list_entries(config, :index, field.associated_collection_scope, false).map { |o| [o.send(field.associated _object_label_method), o.send(field.associated_primary_key)] }.sort_by {|a| [selected_ids.index(a[1]) || selected_ids.si ze, i+=1] }
I get this error
undefined method `klass' for nil:NilClass`
Can anyone help me with this association how can I fix it.
perhaps a late response, but could you compare the relevant part of your database schema with the following and let me know the difference? I believe the relations are setup correctly so that should be the problem.
create_table "companies", force: :cascade do |t|
t.string "name"
end
create_table "company_talent_infos", force: :cascade do |t|
t.string "metadata"
t.integer "company_id"
t.integer "talent_id"
end
create_table "talents", force: :cascade do |t|
t.string "name"
end

How to set up my model-relations properly

I have 3 models: Hacks, Votes, Users.
A user can create many hacks.
Each user should be able to vote on each hack ONCE (Rating of 1-5. Rating should be updateable in case of a missclick or whatever).
I thought about the following relations:
Hack.rb
belongs_to :user
User.rb
has_many :hacks
Votes.rb
belongs_to :user
belongs_to :hack
Is that correct or am I missing something?
I thought about getting all the votes like this later on:
Hack.first.votes
What kind of foreign-keys do I have to set up?
In my schema.rb I already successfully set my users<=>hack relation up, without any foreign keys.
ActiveRecord::Schema.define(version: 20141019161631) do
create_table "hacks", force: true do |t|
t.string "url"
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "email", null: false
t.string "crypted_password", null: false
t.string "salt", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "role"
end
end
Thank you very much in advance!
I think this is what you want to have.
class User.rb
has_many :votes
has_many :hacks, through: :votes
end
class Vote.rb
belongs_to :user
belongs_to :hack
end
class Hack.rb
has_many :votes
end
With this, a hack has many votes through a user.
foreign keys:
votes table: user_id, hack_id
You should be able to do hack.votes
EDIT:
I edited the model to reflect a normal has many through relationship
user -> vote <- hack
a user has many votes
a user has many hacks through votes
a hack has many votes
foreign keys live in the votes table. You can use the following when creating the votes table to indicate the foreign key
t.references user
t.references hack

Rails Associations - Simple Join

New working with Rails 4 and have a question on associations.
Let's say I have a Car
create_table "cars", force: true do |t|
t.string "name"
t.integer "owner_id"
t.integer "mechanic_id"
end
And I have some users which roles are "Owner" and "Mechanic"
create_table "users", force: true do |t|
t.string "name"
t.integer "role_id"
end
Now when I get a list of Cars, I want the names of the Owner and the Mechanic...
I know this sounds easy but i've been banging my head around the associations and not getting anywhere.
I want to display this:
name owner mechanic
Ferari Paul James
with this schema
User
id first role
1 Paul owner
2 James mechanic
Car
name owner_id mechanic_id
Ferrari 1 2
Any help would be much appreciated.
You have two associations to the same model. So in your model/car.rb write:
class Car < ActiveRecord::Base
belongs_to :owner, class_name: 'User'
belongs_to :mechanic, class_name: 'User'
end
and you can use car.owner.name and car.mechanic.name.
See rails guide on associations

Resources