Rails 4 Migration | Add Table with Reference - ruby-on-rails

I am attempting to create a Collaboration table in my Rails 4 project, but I've run into an issue. I wish it to belong_to a single user, the collaborator.
I ran the following command to generate the model and the migration, which I've also copied below.
rails generate model Collaboration project:references collaborator:references accepted:boolean
Migration:
class CreateCollaborations < ActiveRecord::Migration
def change
create_table :collaborations do |t|
t.references :project, index: true, foreign_key: true
t.references :collaborator, index: true, foreign_key: true
t.boolean :accepted
t.timestamps null: false
end
end
end
Model:
class Collaboration < ActiveRecord::Base
belongs_to :project
belongs_to :collaborator, class_name: 'User'
end
I updated the Collaboration model to include , class_name: 'User' as shown above. Similarly, I updated the existing Strategy model to include a has_many :collaborations
class Project < ActiveRecord::Base
has_many :collaborations
end
When I run rake db:migrate, I get the following error reported.
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::UndefinedTable: ERROR: relation "collaborators" does not exist
I'm a bit puzzled as to wy this is happening. Any assistance would be greatly appreciated! Thank you. :)
EDIT:
Adding code for my User model as well.
class User < ActiveRecord::Base
authenticates_with_sorcery!
has_many :projects
has_many :collaborations
end
I edited out validations for fields such as password, email, etc to try to remove clutter.

This part of your migration:
t.references :collaborator, index: true, foreign_key: true
will try to create a foreign key inside the database so that the collaborator_id column of the collaborations table will be guaranteed to be NULL or contain the id of a column in the collaborators table. You can't create that FK until the collaborators table exists.
The error you're getting is:
relation "collaborators" does not exist
and that's just telling you that you don't have a collaborators table but you're trying to reference it.
You need a migration to create the collaborators table before you create your collaborations table.

In Rails 5, at least, you can use foreign_key: {to_table: ... }} as follows.
create_table :messages, id: :uuid do |t|
t.references :from_user, type: :uuid, index: true, null: false, foreign_key: {to_table: :users, on_delete: :cascade}
t.references :to_user, type: :uuid, references: :user, index: true, null: false, foreign_key: {to_table: :users, on_delete: :cascade}
t.text :body, null: false
t.timestamps
end

sorry for being late, but essentially it's all about convenience, remember that's the essence of rails. so; every reference should be targeting the table that should be in the plural (since a table holds many "objects") therefore, you must make the reference to plural so rails will generate a reference to a singular object. button line, your migration should look more like;
class CreateCollaborations < ActiveRecord::Migration
def change
create_table :collaborations do |t|
t.references :projects, index: true, foreign_key: true
t.references :collaborators, index: true, foreign_key: true
t.boolean :accepted
t.timestamps null: false
end
end
end
Now, if you follow the conventions, then you should have no problem with the rest, just keep in mind that belong_to is to a singular object and has_many is to a plural object.
PS: I would not use past reference for the column, like accepted
Happy Coding

Related

Why my optional:true option is not working in my Rails model?

I am struggling with the following migration:
class CreatePartnerActivationHistories < ActiveRecord::Migration[6.1]
def change
create_table :partner_activation_histories do |t|
t.references :partner, null: false, foreign_key: true
t.references :owner, null: false, foreign_key: { to_table: 'admin_users' }
end
end
And the model:
class PartnerActivationHistory < ApplicationRecord
belongs_to :partner
belongs_to :owner, class_name: "AdminUser", optional: true
end
When I try to create a PartnerActivationHistory record without an owner, it raises the following error:
PG::NotNullViolation: ERROR: null value in column "owner_id" of relation "partner_activation_histories" violates not-null constraint
I can't figure it out why my optional: true is not working...
Any ideas? Thanks!
An optional owner would mean an owner_id of nil/NULL in the database, but your spec for that column says it is mandatory and can't be null.
Remove the non-null requirement from the column like this and you should be good:
class CreatePartnerActivationHistories < ActiveRecord::Migration[6.1]
def change
create_table :partner_activation_histories do |t|
t.references :partner, null: false, foreign_key: true
t.references :owner, foreign_key: { to_table: 'admin_users' }
end
end
You'll want to rollback your current migration, make the change to the migration file, then migrate forward again. Assuming you're okay with losing all of the current PartnerActivationHistory records. If not, you'll need to make a new migration that just modifies that one column by removing the non-null constraint.

Ruby on rails - How to associate with generated folders that are nested/sub folders of other folders

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"

Rails - Best way to save "Given Answer" in database?

I'm making a quiz app with Ruby on Rails where I have a model "Answer". Now I need to save the answer a user gives for a question to the database, so I thought I'll make a model "GivenAnswer" and matching controller with these existing models as attributes:
"User"
"Question"
"Answer"
I'm going to put all the logic for analyzing the given answers into the "GivenAnswers" controller, but am not sure how the db migration should look. Also, I would like to have indexes on "User" and "Question", since I'm going to frequently display "answers per user" and "answers per question". I generated this migration:
class CreateGivenAnswers < ActiveRecord::Migration[5.0]
def change
create_table :given_answers do |t|
t.references :user, foreign_key: true
t.references :question, foreign_key: true
t.references :answer, foreign_key: true
t.timestamps
end
end
end
But I'm wondering if the table should be purely relational instead:
class CreateGivenAnswers < ActiveRecord::Migration[5.0]
def change
create_table :given_answers, id:false do |t|
t.belongs_to :user, index: true
t.belongs_to :question, index: true
t.belongs_to :answer
end
end
end
I'm a Rails beginner, so I'd be thankful for any pointers.
belongs_to is an alias for references so it makes no difference which of those you use.
Don't remove the id column in this case.
Any foreign key should also be an index.
I would do this:
class CreateGivenAnswers < ActiveRecord::Migration[5.0]
def change
create_table :given_answers, id:false do |t|
t.references :user, index: true, foreign_key: true
t.references :question, index: true, foreign_key: true
t.references :answer, index: true, foreign_key: true
end
end
end
But I don't think that you should relate Answer directly to GivenAnswer. It would make more sense to relate Answer to Question

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 database constraint for self referential association

I'm building a rails website, which involves a directed friendship relation. I know in model level, it is a self referential association. And there are methods like has_and_belongs_to for that association.
My question is: how can I set up the database level constraints for this relation. I guess the migration would be something like this, which uses foreign keys to guarantee the referential integrity:
class CreateFriendships < ActiveRecord::Migration
def change
create_table :friendships do |t|
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :user, null: false, foreign_key: true
t.integer :accepted, null: false, default: 0
end
end
But when I run rake db:migrate, it has error:
PG::DuplicateObject: ERROR: constraint "fk_friendships_user_id" for relation "friendships" already exists
As a matter of fact, I'm not even sure whether it is necessary for me to set up the database constraint in this case, since I've seen some people's implementation of friendship relation has no database constraint like this:
create_table :friendships do |t|
t.integer :user_id
t.integer :friend_id
t.timestamps
end
According to Rails Guide
The Active Record way claims that intelligence belongs in your models, not in the database. As such, features such as triggers or constraints, which push some of that intelligence back into the database, are not heavily used.
I'm not sure whether in this case, the database constraints are heavily used.
So is it really necessary for me to set up database level constraints (using foreign keys) in this case? Or I just need to realize the constraints in model level? Thanks!!
You have declared user relation twice:
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :user, null: false, foreign_key: true
Seems that it should be like this:
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :friend, null: false, foreign_key: true
To answer your question: how can I set up the database level constraints for this relation?
Answer: Just like you already have.
Often developers go the rails way and set these constraints in model, but it's perfectly reasonable to set them up in database.
EDIT:
This will let you create a table with friend_id
class CreateFriendships < ActiveRecord::Migration
def change
create_table :friendships do |t|
t.belongs_to :user, null: false, foreign_key: true
t.integer :friend_id, null: false
t.integer :accepted, null: false, default: 0
end
add_foreign_key :friendships, :users, column: :friend_id
end
end
I think you're getting confused about the role of foreign_keys in your database architecture.
ActiveRecord is just a "coating" for SQL.
It's able to form queries etc which allow you to build associated objects, thus the most important thing you can do is associate those objects properly.
The way to do this - in SQL - is to use a foreign_key, which essentially shows the likes of ActiveRecord (and SQL if you use a join query) which data is associated:
Foreign keys are a standard element of relational database structures, which you probably know.
The reason why your data structure is failing is due to the fact you've replicated the user_id foreign key in your friendships table.
You'll want to refer to the following:
Rails: self join scheme with has_and_belongs_to_many?
This shows you that if you want to create a self referential join table (such as you're doing), you need to use the following:
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :friends,
class_name: "User",
join_table: :friendships,
foreign_key: :user_id,
association_foreign_key: :friend_user_id
end
#db/migrate/______.rb
class CreateFriendships < ActiveRecord::Migration
def self.up
create_table :friendships, id: false do |t|
t.integer :user_id
t.integer :friend_user_id
end
add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
end
def self.down
remove_index(:friendships, [:friend_user_id, :user_id])
remove_index(:friendships, [:user_id, :friend_user_id])
drop_table :friendships
end
end
Notice how the references are for user_id and friend_user_id?
These are the two foreign keys you need to make sure your has_and_belongs_to_many is able to associate two objects of the same model.

Resources