rails dependent: :destroy works with wrong ID value - ruby-on-rails

my schema.db
create_table 'pet', force: :cascade do |t|
t.string 'name'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
end
create_table 'user', force: :cascade do |t|
t.string 'name'
t.references :pet, index: true, foreign_key: { on_delete: :nullify }
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
end
user model
class User < ApplicationRecord
has_one :pet, foreign_key: :id, dependent: :destroy
end
pet model
class Pet < ApplicationRecord
belongs_to :user, optional: true
end
show.erb
<%= link_to t('delete'), user_path(id: #user.id), method: :delete %>
<%= link_to t('.pet_delete'), pet_path(#auser.pet_id), method: :delete %>
user controller
def destroy
User.destroy(params[:id])
redirect_to users_path
end
pet controller
def destroy
Pet.destroy(params[:id])
redirect_to users_path
end
Problem :
if i delete user id 5 with pet id 5,
dependent destroy works well and it deletes (user id 5) & (pet id 5)
if i have user id 5 with pet id nil,
and user id 4 with pet id 5,
when i delete user id 5, user 5 deleted with user4's pet (pet id 5)
so leftover result -> user id 5 delete, user id 4 with nil pet
something is wrong but i can't find what is problem.
dependent destroy can only find same userId and petId in the same time.
i expecting if i delete user id 3 with pet id 1, want to make delete well
i tried change position of dependent destroy(on pet model),
change schema.rb's on_delete nullify to delete but not worked.

The problem is that you specified id as a foreign key.
class User < ApplicationRecord
has_one :pet, foreign_key: :id, dependent: :destroy
end
You need to specify user_id instead:
class User < ApplicationRecord
has_one :pet, foreign_key: :user_id, dependent: :destroy
end
But, in a simple case like this you don't need to specify foreign key at all. As long as you have a column named user_id in pets table it will just work, since Rails automatically assumes that the foreign key should be <model>_id.
Also, please keep in mind that the foreign key should be added to the child, not the parent. So, you need to add user_id to pets table, not pet_id to the users table.
In the end, your schema.rb should look something like this:
create_table 'pet', force: :cascade do |t|
t.string 'name'
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
t.references :user, index: true, foreign_key: { on_delete: :nullify } # <== this should be added
end
create_table 'user', force: :cascade do |t|
t.string 'name'
# t.references :pet, index: true, foreign_key: { on_delete: :nullify } <== this should be removed
t.datetime 'created_at', precision: 6, null: false
t.datetime 'updated_at', precision: 6, null: false
end

Related

Rails: Self-referential associations have my head spinning

I'm currently working on a small school project that utilizes Ruby on Rails and I'm having some trouble getting my self-referential associations working correctly.
Context
The intended functionality of my web app is for users to post houses/apartments for other users to search through and rent. Since I'm having issues with a specific association, I'm working with a completely stripped down version that only has two models, User and Lease.
What I'm Trying to Accomplish
Ideally, when a person first registers on the site, a User object is created to hold their information such as email and password. A User can then either post a listing or search through listings.
Once a post has been created and another user decides to rent the posted house, a Lease object is created, which holds the ID of the posting User as well as the ID of the renting user, aliased as "landlord_id" and "tenant_id" respectively.
A User should now be identified as either a User, Landlord or a Tenant (or both Landlord and Tenant) based on whether there are any Lease objects with their ID as either a Landlord or a Tenant. This identification will be used to determine whether the User can access other areas of the site.
userFoo.leases
This should give me a list of all Lease objects with which the User's ID is associated, regardless of whether it's as a Landlord or Tenant.
userFoo.tenants
This should give me a list of any User object whose ID is associated with the ID of userFoo as a Tenant through Lease, and the inverse if I ask for landlords.
The Code
User Class
class User < ApplicationRecord
has_many :tenants, class_name: "Lease", foreign_key: "landlord_id"
has_many :landlords, class_name: "Lease", foreign_key: "tenant_id"
end
Lease Class
class Lease < ApplicationRecord
belongs_to :landlord, class_name: "User"
belongs_to :tenant, class_name: "User"
end
Users Table Migration
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.string :password_digest
t.timestamps
end
end
end
Leases Table Migration
class CreateLeases < ActiveRecord::Migration[6.0]
def change
create_table :leases do |t|
t.references :landlord, null: false, foreign_key: {to_table: :users}
t.references :tenant, null: false, foreign_key: {to_table: :users}
t.timestamps
end
end
end
Database Schema
ActiveRecord::Schema.define(version: 2020_10_18_005954) do
create_table "leases", force: :cascade do |t|
t.integer "landlord_id", null: false
t.integer "tenant_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["landlord_id"], name: "index_leases_on_landlord_id"
t.index ["tenant_id"], name: "index_leases_on_tenant_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
add_foreign_key "leases", "users", column: "landlord_id"
add_foreign_key "leases", "users", column: "tenant_id"
end
What's Wrong?
userFoo.leases
Normally a User would have_many leases by having their ID associated with a lease as "user_id." However, since I'm using "tenant_id" and "landlord_id", this command fails because it can't find "user_id" in the Leases table.
userFoo.tenants
This command gives me a list of all Lease objects where userFoo's ID is associated as "landlord_id" instead of all User objects associated with userFoo's ID as tenants. To retrieve a tenant as is, I have to use the command:
userFoo.tenants.first.tenant
Conclusion
I am having a bit of a hard time understanding these deeper, more complex associations, and I've spent some time trying to find a detailed reference on has_many that covers all the arguments, but all I can really find are small blog posts that reference the "Employees" and "Managers" example on guides.rubyonrails.com . I think one problem is that I'm not sure I'm correctly reflecting my model associations in my table schema.
I'm more than happy to teach myself if someone can point me in the right direction. I'm also open to alternative solutions but only if I can't get the functionality I want out of this setup, because my instructor specifically asked me to try it this way
Thanks in advance for any help! It's much appreciated.
as per your requirement you can try like this:
# app/models/user.rb
class User < ApplicationRecord
has_many :owned_properties, class_name: "Property", foreign_key: "landlord_id"
has_many :rented_properties, class_name: "Property", foreign_key: "tenant_id"
end
Here I have declared two associations with same table but different foreign keys.
# app/models/property.rb
class Property < ApplicationRecord
belongs_to :landlord, class_name: "User"
belongs_to :tenant, class_name: "User"
end
Here I have taken one table by using this user can post one property where landlord is the owner of a house and later you can add tenant who is taking rent to one property.
# db/migrations/20201018054951_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, index: true
t.string :password_digest, null: false
t.timestamps
end
end
end
Above is your users table migration.
# db/migrations/20201018055351_create_properties.rb
class CreateProperties < ActiveRecord::Migration[6.0]
def change
create_table :properties do |t|
t.references :landlord, foreign_key: {to_table: :users}, null: false
t.references :tenant, foreign_key: {to_table: :users}
t.timestamps
end
end
end
Above is your properties table migration.
# db/schema.rb
ActiveRecord::Schema.define(version: 2020_10_18_055351) do
create_table "properties", force: :cascade do |t|
t.bigint "landlord_id", null: false
t.bigint "tenant_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["landlord_id"], name: "index_properties_on_landlord_id"
t.index ["tenant_id"], name: "index_properties_on_tenant_id"
end
create_table "users", force: :cascade do |t|
t.string "name", null: false
t.string "email", null: false
t.string "password_digest", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email"
end
add_foreign_key "properties", "users", column: "landlord_id"
add_foreign_key "properties", "users", column: "tenant_id"
end
If you want to fetch all the owned properties of a user, use user.owned_properties.
If you want to fetch all rented properties of a user, use user.rented_properties.
^^ Here both the cases you'll get objects of Property class.
If you want to get landlord of a property, use property.landlord.
If you want to get tenant of a property, use property.tenant.
^^ Here both the cases you'll get objects of User class.
If you want you can add other attributes like: name, price, etc to properties table.
I think, this will help you. Thanks :) Happy Coding :)

How to query an object from one model, starting from another model? Ruby

I am trying to make an association that is not working.
I have the following scope:
ActiveRecord::Schema.define(version: 2020_04_05_125812) do
create_table "accounts", force: :cascade do |t|
t.string "social_network"
t.string "name_account"
t.integer "person_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["person_id"], name: "index_accounts_on_person_id"
end
create_table "lists", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "lists_people", id: false, force: :cascade do |t|
t.integer "list_id", null: false
t.integer "person_id", null: false
end
create_table "people", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "posts", force: :cascade do |t|
t.string "post_text"
t.date "date"
t.string "link"
t.integer "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_posts_on_account_id"
end
add_foreign_key "accounts", "people"
add_foreign_key "posts", "accounts"
end
I wish I could consult, for example:
I want to consult the person named "Test_name"
That person belongs to lists, which is a collection of people. In addition, that person has accounts and those accounts have Posts.
class List < ApplicationRecord
has_and_belongs_to_many :people
end
class Person < ApplicationRecord
has_and_belongs_to_many :lists
end
class Account < ApplicationRecord
has_many :posts
belongs_to :person
end
class Post < ApplicationRecord
belongs_to :account
end
How could I have a return like the one below:
List | Name | social_network
1 | Test_name | facebook
2 | Test_name | twitter
All the queries I make, either return the wrong type, or return only the list.
Problems such as:
List | Name | social_network
1 | Test_name | facebook
2 | Test_name | twitter
1 | Second_name | twitter
And I don't want to see the data "second_name"
I try this:
#lists = List.from(
Person.left_outer_joins(:list).where('people.name LIKE ?', "Renata Rempel"),
:list
)
but, doesn't works =/
To start off with you want to setup a many to many association between Person and List. This can be done with has_and_belongs_to_many but there are many reasons why has_many through: may be a better choice. The primary one is that it will let you add features like keeping track of banned users or when a user joined a list.
# rails g model list_membership member:belongs_to user:belongs_to
class ListMembership < ApplicationRecord
belongs_to :member, class_name: 'Person'
belongs_to :list
end
We then have to fix the foreign key in the association:
class CreateListMemberships < ActiveRecord::Migration[6.0]
def change
create_table :list_memberships do |t|
t.belongs_to :list, null: false, foreign_key: true
t.belongs_to :member, null: false, foreign_key: { to_table: :people }
t.timestamps
end
# can be a good idea to add a compound index
# add_index [:list_id, :member_id], unique: true
end
end
class Person < ApplicationRecord
has_many :list_memberships, foreign_key: :member_id
has_many :lists, through: :list_memberships
has_many :accounts
has_many :posts, through: :accounts
end
class List
has_many :list_memberships
has_many :members,
through: :list_memberships
end
Your from query will not work as your actually selecting rows from people but you just alias the table lists. That won't magically select the right data! If you really wanted to use from you would do:
List.from(
List.joins(:members).where("people.name LIKE ?", "Renata Rempel"),
:lists
).eager_load(members: :posts)
If you want to create a bunch of lists with a random number of members in your seed file you can just do:
ids = 10.times.map do
Person.create!(name: Faker::Name.name).id
end
lists = 10.times.do
List.create!(member_ids: ids.sample(2))
end

Unitialized constant User:Bookings when trying to add data into a join table

I have a User table and a Booking Table that is linked by a create_join_table what holds the user id and booking ids. When a user books a room, i need the id of both the user and new booking to go into that. I am getting the error above and im not sure why.
I have looked online and saw something similar, their class names were plural however I don't think I have that.
booking.rb
class Booking < ApplicationRecord
enum room_type: ["Basic Room", "Deluxe Room", "Super-Deluxe Room", "Piton Suite"]
has_many :join_tables
has_many :users, through: :join_tables
end
user.rb
class User < ApplicationRecord
has_secure_password
validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, presence: true, uniqueness: true
has_many :join_tables
has_many :bookings, through: :join_tables
end
join_table.rb
class JoinTable < ApplicationRecord
belongs_to :users
belongs_to :bookings
end
bookings_controller.rb
def create
#booking = Booking.create(booking_params)
current_user.bookings << #booking ##Where the error happens
db/schema
ActiveRecord::Schema.define(version: 2019_12_13_181019) do
create_table "bookings", force: :cascade do |t|
t.integer "room_type"
t.date "check_in"
t.date "check_out"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "join_tables", force: :cascade do |t|
t.integer "users_id"
t.integer "bookings_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["bookings_id"], name: "index_join_tables_on_bookings_id"
t.index ["users_id"], name: "index_join_tables_on_users_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
I have just tried to reproduce your problem and I have a similar exception
irb(main):003:0> User.first.bookings
NameError (uninitialized constant User::Bookings)
but, when I change
belongs_to :users
belongs_to :bookings
to
belongs_to :user
belongs_to :booking
in app/models/join_table.rb everything works as expected.
This is how I created the JoinTable model
$ rails generate model JoinTable
class CreateJoinTables < ActiveRecord::Migration[6.0]
def change
create_table :join_tables do |t|
t.references :user
t.references :booking
t.timestamps
end
end
end
As you can see in the belongs_to docs, it is used in the singular form most of the time.

unknown attribute with polymorphic association

In my online shop I have tables Product and Size, also I think I need to add a table Restocking
Instead of updating a product, I guess It's better to have a Restocking table then I could track the dates where I added any new sizes, quantity, and why not the new prices (buying and selling)... and create stats...
Do you this it is correct?
Once a Restocking is created, the corresponding Product is updated with new quantity and price?
Well,
So it started this way:
#Product
has_many :sizes
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Size
belongs_to :product
The Restocking table needs to have sizes attributes (like product)
I believe that I have to use polymorphic associations, but how I am supposed to update my schema , what should I add, remove?
So since I added the Restocking model, my models look like this:
#Product
has_many :sizes, inverse_of: :product, dependent: :destroy, as: :sizeable
has_many :restockings
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Restocking
has_many :sizes, as: :sizeable
belongs_to :product
accepts_nested_attributes_for :sizes, reject_if: :all_blank, allow_destroy: true
#Size
belongs_to :product
belongs_to :restocking
belongs_to :sizeable, polymorphic: true, class_name: "Size"
schema.rb
create_table "sizes", force: :cascade do |t|
t.string "size_name"
t.integer "quantity"
t.bigint "product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "quantity_stock"
t.index ["product_id"], name: "index_sizes_on_product_id"
end
create_table "restockings", force: :cascade do |t|
t.bigint "product_id"
t.bigint "sizeable_id"
t.decimal "price", precision: 10, scale: 2
t.decimal "buying_price", precision: 10, scale: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["product_id"], name: "index_restockings_on_product_id"
t.index ["sizeable_id"], name: "index_restockings_on_sizeable_id"
end
create_table "products", force: :cascade do |t|
t.string "title", limit: 150, null: false
t.text "description"
t.bigint "category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "color"
t.integer "user_id"
t.json "attachments"
t.string "brand"
t.string "ref"
t.decimal "price"
t.decimal "buying_price", precision: 10, scale: 2
t.index ["category_id"], name: "index_products_on_category_id"
end
At this point I have several errors, like
in ProductsController
def new
#product = Product.new
#product.sizes.build
end
error:
ActiveModel::UnknownAttributeError at /admin/products/new
unknown attribute 'sizeable_id' for Size.
Can you light me on the migrations I have to change?
Suggestions are welcome
You're almost there, to use polymorphic inside your Size model, you have to change the size resource, and add two attributes to the resource: sizeable_id and sizeable_type.
The sizeable_type is a string, indicates the class of the parent element, in your case, can be Product or Restocking, and sizeable_id indicates the element_id to find the parent element, your relations are correct, but you must add this elements to your Size, see the following:
One exemple of a migration to your case:
class AddSizeableToSize < ActiveRecord::Migration
def change
add_reference :sizes, :sizeable, polymorphic: true, index: true
end
end
On your Size model:
# app/models/size.rb
class Size < ActiveRecord::Base
belongs_to :sizeable, polymorphic: true
end
In your Product or Restocking model:
has_many :sizes, as: :sizeable
This is just a simple way to make your case works! If you want to know more about rails associations and polymorphism, can take a look in this link.

Rails 5 association with different foreign key

I think I have missed something when creating an association with a foreign_key in Rails.
I have 2 models: Company and Employee. Company has_many employees and Employee belongs_to a company. Company has an attribute called company_code and I should be able to figure out which company the employee works for using the company_code instead of company_id.
At first, I created the models:
rails g model Company company_code:integer:index name
rails g model Employee joined:date:index salary:integer
Then, I generated a migration to add the company_code column to the employees table.
class AddReferenceToEmployee < ActiveRecord::Migration[5.1]
def change
add_column :employees, :company_code, :integer, index: true
add_foreign_key :employees, :companies, column: :company_code
end
end
And, finally I added the foreign key at the model level.
class Company < ApplicationRecord
has_many :employees, foreign_key: :company_code
end
class Employee < ApplicationRecord
belongs_to :company, foreign_key: :company_code
end
However, I'm still not able to create proper association.
company = Company.create(name: 'test', company_code: 123)
company.employees.create(joined: Date.today, salary: 1000)
It creates employee record with company_code = 1 instead of 123.
When I try to create a new instance of employee
company.employees.new
It will generate
#<Employee id: nil, joined: nil, salary: nil, created_at: nil, updated_at: nil, company_code: 1>
What am I missing? Is this the right way to do it?
Bellow is my schema.rb
ActiveRecord::Schema.define(version: 20180828052633) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "companies", force: :cascade do |t|
t.integer "company_code"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["company_code"], name: "index_company_on_company_code"
end
create_table "employees", force: :cascade do |t|
t.date "joined"
t.integer "salary"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "company_code"
end
add_foreign_key "employees", "companies", column: "company_code"
end
class Company < ApplicationRecord
has_many :employees, primary_key: :company_code, foreign_key: :company_code
end
class Employee < ApplicationRecord
belongs_to :company, foreign_key: :company_code, primary_key: :company_code
end

Resources