Dependent objects not destroyed when parent object is destroyed - ruby-on-rails

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

Related

Custom Association in ruby on rails

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.

Rails - has_many through, STI, and scope

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.

Rails 5.2 - how to associate a Report model with two Users

In a threaded discussion context, I want to allow Users to report offensive Comments. I have a Reports table in which I want to record both the User who wrote the Comment and the User who made the complaint.
My Reports table is currently as follows:
create_table "reports", force: :cascade do |t|
t.bigint "comment_id"
t.bigint "user_id"
t.integer "author_id"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["comment_id"], name: "index_reports_on_comment_id"
t.index ["user_id"], name: "index_reports_on_user_id"
end
user_id records the id the user making the complaint. author_id records the id of the author of the comment.
The associations are set up as follows:
report.rb
class Report < ApplicationRecord
belongs_to :comment
belongs_to :user
belongs_to :author, class_name: 'User', foreign_key: 'author_id'
end
user.rb
class User < ApplicationRecord
# Nothing yet, but presumably need something
end
In the console, I can query as follows:
r = Report.last
=> #<Report id: ...
r.user
=> #<User id: ...
but I can't query
r.author
=> NoMethodError: undefined method `author' for #<Report:0x000000082ef588>
So the first question is how can I enable this type of query.
Secondly, I'd like to be able to query the complaints (Reports) made against particular Users, so:
u = User.last
=> #<User id: ...
u.complaints
=> #<ActiveRecord::AssociationRelation [...
What do I need to add to enable these queries too?
class Report < ApplicationRecord
belongs_to :comment
belongs_to :user
belongs_to :author, class_name: 'User', foreign_key: 'author_id'
end
class User < ApplicationRecord
# Nothing yet, but presumably need something
has_many :reports #user reports
#Query the complaints (Reports) made against particular Users
has_many :complaints, class_name: 'Report', foreign_key: 'author_id'
end

Rails model holds several references of its own class type in the same table

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!

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?

Resources