I am finding it hard to understand some of the association defined in the code base.
class Patient < ApplicationRecord
belongs_to :g_district, class_name: "District", primary_key: "id", foreign_key: 'district_id', optional: true
belongs_to :g_perm_district, class_name: "District", primary_key: "id", foreign_key: 'permanent_district_id', optional: true
belongs_to :g_workplc_district, class_name: "District", primary_key: "id", foreign_key: 'workplace_district_id', optional: true
end
class District
belongs_to :province #, optional: true
belongs_to :division, optional: true
has_many :hospitals
has_many :tehsils
has_many :ucs
has_many :mobile_users
has_many :labs
has_many :insecticides
end
I am not clearly getting these kind of associations defined her.(belongs_to :g_district, class_name: "District", primary_key: "id", foreign_key: 'district_id', optional: true).
In my code, there are no models like g_district, g_perm_district, g_workplc_district.
Your code is associating the same model under different names. When in doubt you can always check this by looking at class_name: "District" which is referencing the actual class name.
In your case a Patient can have an association with three different districts (but all of them point to the District model):
3.times { District.create }
patient = Patient.create(
g_district: District.find(1),
g_perm_district: District.find(2),
g_workplc_district: District.find(3)
)
patient.g_district #=> #<District:0x00000001083ce048 id: 1,.. >
patient.g_perm_district #=> #<District:0x00000001083ce048 id: 2,.. >
patient.g_workplc_district #=> #<District:0x00000001083ce048 id: 3,.. >
It might also be worth checking the migrations or schema for Patient.
The schema table might look like this:
create_table "patients", force: :cascade do |t|
t.integer "district_id"
t.integer "permanent_district_id"
t.integer "workplace_district_id"
(...other columns here...)
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
and the migration:
class CreatePatients < ActiveRecord::Migration[7.0]
def change
create_table :patients do |t|
t.integer :district_id
t.integer :permanent_district_id
t.integer :workplace_district_id
(...other columns here...)
t.timestamps
end
end
end
The foreign_key: 'district_id' part (as well as the other two) suggests that these columns must exist and helps ActiveRecord properly associate the same model multiple times
This is simply creating an association with the same table with different names.
Here g_district, g_perm_district, g_workplc_district are associating with District model with the association names: g_dstrict with foreign_key district_id, g_perm_district with foreign_key permanent_district_id and g_workplc_district with foreign_key workplace_district_id
This basically states that Patients can have 3 districts- District, Permanent District, and Workplace District, as all these 3 are types of District, created an association with the same table with a different names.
Refer this blog for more details.
Related
I have a folder named admin that has a generated scaffolding in it named products that also has the primary_key, id, changed to ect. I then created a model called cart_products that has a belongs_to :product. When I try to use it like:
#cart.cart_products.create(product: #product, quantity:), it throws a name error, saying
Rails couldn't find a valid model for Product association. Please provide the :class_name option on the association declaration. If :class_name is already provided, make sure it's an ActiveRecord::Base subclass.
So I then changed the belongs_to to belongs_to :product, :class_name => "Admin::Product" which is the name of the product model. Now I am getting an
ActiveRecord::StatementInvalid - SQLite3::SQLException: no such table: main.products
Where did main.products come from when in my database it is saved as create_table "admin_products", primary_key: "ect", force: :cascade do |t|?
This is what My code looks like:
# controllers/home/cart_controller.rb
class Home::CartController < HomeController
def add
#product = Admin::Product.find_by(ect: params[:ect])
# Code breaks on next line
#cart.cart_products.create(product: #product, quantity:)
end
end
# models/cart_product.rb
class CartProduct < ApplicationRecord
belongs_to :product, class_name: "Admin::Product"
belongs_to :cart
end
# models/admin/product.rb
class Admin::Product < ApplicationRecord
has_many :cart_products
has_many :carts, through: :cart_products
end
end
# models/admin.rb
module Admin
def self.table_name_prefix
"admin_"
end
end
The Database that I am trying to access is:
# associated with models/admin/product.rb
create_table "admin_products", primary_key: "ect", force: :cascade do |t|
t.string "title"
t.decimal "price"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
# associated with models/cart_product.rb
class CreateCartProducts < ActiveRecord::Migration[7.0]
def change
create_table :cart_products do |t|
t.belongs_to :product, null: false, foreign_key: true
t.belongs_to :cart, null: false, foreign_key: true
t.integer :quantity
t.timestamps
end
end
end
You have to tell rails the table name:
# app/models/admin/product.rb
module Admin
class Product < ApplicationRecord
self.table_name = "admin_products"
end
end
Or add prefix to every table for models in Admin module.
# app/models/admin.rb
module Admin
def self.table_name_prefix
"admin_"
end
end
Update
Rollback your CreateCartProducts migrations and update it to fix foreign key constraint:
# NOTE: by default when foreign key constraint is created
# the name of the foreign table is inferred from
# the argument `:product`. There is no `products` table,
# which is why SQLite complains about it in the error;
# custom primary key has to be also specified.
# t.belongs_to :product, null: false, foreign_key: true
t.belongs_to :product, null: false,
foreign_key: { to_table: :admin_products, primary_key: "ect" }
Run migrations again. This should fix it.
Also, it's probably best to set up PostgreSQL for development. It would have raised an error when trying to run that migration, but SQLite seems to be ok with migration but complains later.
https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-table_name
https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference
Always run after scaffolding with any model
rails db:migrate
Is your model file admin/product.rb
class Admin::Product < ApplicationRecord
If yes, then you need to have class_name with associations as below
belongs_to :product, class_name: "Admin::Product"
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
I have the following models:
create_table :production_actions do |t|
t.string :name
t.string :key
end
create_table :production_attributes do |t|
t.string :name
t.string :key
t.string :type
end
create_table :production_items do |t|
t.timestamps
end
create_table :line_items_production_items do |t|
t.references :line_item, null: false
t.references :production_item, null: false
end
add_index(:line_items_production_items, [:line_item_id, :production_item_id], unique: true, name: 'line_items_production_items_unique')
create_table :production_attributes_production_items do |t|
t.references :production_attribute, null: false, index: {name: 'production_items_on_production_attribute_id'}
t.references :production_item, null: false, index: {name: 'production_attributes_on_production_item_id'}
end
add_index(:production_attributes_production_items, [:production_attribute_id, :production_item_id], unique: true, name: 'production_attributes_production_items_unique')
create_table :production_actions_production_items do |t|
t.references :production_action, null: false, index: {name: 'production_items_on_production_action_id'}
t.references :production_item, null: false, index: {name: 'production_actions_on_production_item_id'}
end
add_index(:production_actions_production_items, [:production_action_id, :production_item_id], unique: true, name: 'production_actions_production_items_unique')
class ProductionAttributeProductionItem < ApplicationRecord
belongs_to :production_attribute, class_name: "ProductionAttribute::Base"
belongs_to :production_item, class_name: "ProductionItem::Base"
end
module ProductionItem
class Base < ApplicationRecord
self.table_name = "production_items"
has_many :production_attribute_production_items, class_name: "ProductionAttributeProductionItem"
has_many :production_attributes, class_name: "ProductionAttribute::Base", through: :production_attribute_production_items
end
module ProductionAttribute
class Base < ApplicationRecord
self.table_name = "production_attributes"
end
end
module ProductionAttribute
class PaperType < ProductionAttribute::Base
end
end
module ProductionItem
class Paper < ProductionItem::Base
has_one :paper_type, -> { where(type: "ProductionAttribute::PaperType")},
class_name: "ProductionAttribute::PaperType", foreign_key: :product_attribute_id,
through: :production_attributes, inverse_of: :product_attribute
end
end
There are many types of production_items that are all saved in a table via STI. There are also many types of production_attributes all saved in a table via STI. A production_item has (and belongs to) many production_attributes. Based off the subclass, i want to create specific product_attribute associations based off the type of production_item. for instance, a paper production_item will have a paper_type product_attribute.
my problem is i am trying to get the specific scoped associations like paper.paper_type to work but i am having a hard time. If i do something like the following i get an error:
[1] pry(main)> p = ProductionItem::Paper.new
(0.3ms) SET NAMES utf8, ##SESSION.sql_mode = CONCAT(CONCAT(##sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), ##SESSION.sql_auto_is_null = 0, ##SESSION.wait_timeout = 2147483
=> #<ProductionItem::Paper:0x00005602a669b528
id: nil,
created_at: nil,
updated_at: nil>
[2] pry(main)> p.paper_type
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) "paper_type" or :paper_type in model ProductionAttribute::Base. Try 'has_many :paper_type, :through => :production_attributes, :source => <name>'. Is it one of jobs?
from /home/USERNAME/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/activerecord-5.2.4.4/lib/active_record/reflection.rb:920:in `check_validity!'
Ive tried a few things without any luck. any idea why this isnt working?
Did you store the foreign keys in the models that are in the database?
To use belongs_to, has_many and has_one you need to store these values in some way in the database.
For example, in ProductionItem you don't necessarily need to store the ids of other objects (it would be interesting, but it would be something similar to an array). But if you decide not to do this above, you must save the id of each ProductionItem in the object that belongs to it.
I have two models, Story and User. Each Story belongs_to to a certain user. However, calling a User's .destroy method does not destroy the Stories it owns (and, of course, this then raises an SQL error because of the foreign key constraint). Here is the relevant code from the models (with irrelevant stuff omitted, obviously):
class Story < ApplicationRecord
belongs_to :user, foreign_key: 'author', primary_key: 'name'
end
class User < ApplicationRecord
self.primary_key = :name
has_many :stories, foreign_key: 'author', primary_key: 'name'
end
And here are the table definitions from schema.rb (again, with irrelevant columns omitted):
create_table "stories", force: :cascade do |t|
t.string "author", null: false
t.index["author"], name: "index_stories_on_author", using: :btree
end
create_table "users", id: false, force: :cascade do |t|
t.string "name", null: false
t.index ["name"], name: "index_users_on_name", unique: true, using: :btree
end
add_foreign_key "stories", "users", column: "author", primary_key: "name"
Things I have tried that do not work:
Removing the foreign key constraint. (This prevents the SQL error, but does not remove the story from the database.)
Adding "dependent: :destroy" to the belongs_to statement.
Random facts that may be relevant but probably aren't: Story also owns some chapter objects. I am using PostgreSQL. I have the composite_primary_keys gem.
I believe you have it a bit backwards. Story depends on User, so the dependent: :destroy directive belongs in User something like this:
class User < ApplicationRecord
self.primary_key = :name
has_many :stories, foreign_key: 'author', primary_key: 'name', dependent: :destroy
end
There seems to be no sequence of associations that work for this pattern:
Each user holds a reference to two OTHER users in the same table.
The User table contains two fields called user_a_id and user_b_id. I've been trying to get the following model associations to work:
class User < ApplicationRecord
has_one :user_a, class_name: "User", foreign_key: "user_a_id"
has_one :user_b, class_name: "User", foreign_key: "user_b_id"
end
The reference only needs to work in one direction. I simply want to use the model in the following way:
user.user_a.name
user.user_b.name
I won't ever need to access user_a.parent_user. I do not need that type of relationship.
The problem occurs when I reference self.user_a in the before_save callback. I basically get a recursive loop of SQL queries that eventually give me a stack too deep error.
Does anyone know what's going on here?
I just tried what you want to achieve. This is the migration for the users table:
create_table :users do |t|
t.string :name
t.references :user_a
t.references :user_b
t.timestamps
end
Notice how this generates the following schema.rb
create_table "users", force: :cascade do |t|
t.string "name"
t.integer "user_a_id"
t.integer "user_b_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_a_id"], name: "index_users_on_user_a_id"
t.index ["user_b_id"], name: "index_users_on_user_b_id"
end
In the User model I have
class User < ApplicationRecord
has_one :user_a, class_name: "User", foreign_key: "user_a_id"
has_one :user_b, class_name: "User", foreign_key: "user_b_id"
end
After migrating I can do in my rails console the following:
User.create(
name: "inception_user",
user_a: User.create(name: "Adam"),
user_b: User.create(name: "Berta")
)
inception_user = User.find_by_name "inception_user"
inception_user.user_a.name
=> "Adam"
inception_user.user_b.name
=> "Berta"
Everything works as expected with this setup. Please comment if you still have problems!
More information about self-joins: http://guides.rubyonrails.org/association_basics.html#self-joins
Finally found a solution. This may be an edge case BUT I needed to use belongs_to instead of has_one and I needed to remove the id from my table and the foreign_key. Also, because I was storing my references in a before_save callback, and they would have been empty during validation, I needed to add the parameter optional: true. This is the only association that allowed my program to reliably work:
class User < ApplicationRecord
belongs_to :user_a, class_name: "User", foreign_key: "user_a", optional: true
belongs_to :user_b, class_name: "User", foreign_key: "user_b", optional: true
end
Hopefully that helps somebody!