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

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

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.

Rails uniqueness constraint on two column values in different models

I want to know the best way to have an uniqueness constraint enforced on two related model attributes in rails that are both no primary keys
class Parent > ApplicationRecord
has_many :children
:name
end
class Child > ApplicationRecord
:name
end
I want to enforce that (parent.name, child.name) is unique for every parent. e.g.
(parent1, child1) and (parent2, child1) is allowed
(parent1, child1) and (parent1, child1) is a violation
Ideally, I would enforce this in Postgres, however I have only seen the option to add uniqueness constraints on multiple columns of the same table.
Alternatively, I have written a custom validator for rails that does what I want, but this is cumbersome. There needs to be a better solution...
For completeness, here is the constraints validator which requires one to add a children function to a model returning the list of children.
class NamePairValidator < ActiveModel::Validator
def validate(record)
record.children.values.each do |model_children|
names = model_children.to_a.collect {|model| model.name}
if (names.select{|name| names.count(name) > 1 }.size > 0)
record.errors[:name] << 'Path leading to this resource has no unique name'
end
end
end
end
(in Parent.rb)
def children
{children: :children}
end
Migrations:
class CreateDomains < ActiveRecord::Migration[5.0]
def change
create_table :domains do |t|
t.string :name
t.string :domain_type
t.timestamps
end
end
end
class CreateSubjects < ActiveRecord::Migration[5.0]
def change
create_table :subjects do |t|
t.string :name
t.string :subject_type
t.timestamps
end
end
end
class CreateJoinTableDomainSubject < ActiveRecord::Migration[5.0]
def change
create_join_table :domains, :subjects do |t|
t.index [:domain_id, :subject_id]
t.index [:subject_id, :domain_id]
end
end
end
I just used similar one in my code
validates :child_name, uniqueness: { scope: :parent_id }
More..
(i) https://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of
(ii) Validate uniqueness of multiple columns
Insipered by the-has-many-through-association of the official doc of ruby on rails:
class CreateAppointments < ActiveRecord::Migration[5.0]
def change
create_table :domains do |t|
t.string :name, null: false
t.string :domain_type
t.timestamps
end
create_table :subjects do |t|
t.string :name, null: false
t.string :subject_type
t.timestamps
end
create_table :fields do |t|
t.belongs_to :domain, index: true
t.belongs_to :subject, index: true
t.timestamps
end
end
end
Note
I took the initative to rename your model JoinTableDomainSubject by Field to be more readable.
I also force name field not be nil to check uniqueness.
(adding null: false in migrations files and validates :name, presence: true in both models)
Now the dedicated classes:
class Subject < ApplicationRecord
has_many :fields
has_many :domains, through: :fields
validates :name, presence: true
end
class Domain < ApplicationRecord
has_many :fields
has_many :subjects, through: :fields
validates :name, presence: true
end
class Field < ApplicationRecord
belongs_to :domain
belongs_to :subject
validate :domain_and_subject_names_uniqueness
private
def domain_and_subject_names_uniqueness
if class.includes(:domain, subject)
.where(domain: { name: domain.name }, subject: { name: subject.name })
.exists?
errors.add :field, 'duplicity on names'
end
end
end
Since the models are associated, I can use Field.first.domain to access Domain model of a given Field and vice versa.

Do I need a foreign key declaration in the individual table when creating join table?

I have a join table of "contents" and "roles" called content_roles and this is the join table.
class CreateContentRoles < ActiveRecord::Migration
def change
create_table :content_roles, :id => false do |t|
t.belongs_to :content, foreign_key: "content_id"
t.belongs_to :roles, foreign_key: "role_id"
end
add_index :content_roles, ["content_id", "roles_id"]
end
end
So in the individual roles and contents migration, do I need to have a foreign_key that refers back to the join table and/or the roles/contents? Sorry, I didn't explain this any better.
why cant you use references as shown below
class CreateContentRoles < ActiveRecord::Migration
def change
create_table :content_roles, :id => false do |t|
t.references :content, index: true, foreign_key: true
t.references :roles, index: true, foreign_key: true
t.timestamps null: false
end
end
end
The id columns in the content and roles tables act as foreign keys. Rails joins the content and content_roles tables using id in content table and content_id in content_roles table. So are the roles and content_roles tables.

Rails 4 Migration | Add Table with Reference

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

How to add foreign key in rails migration with different table name

How can I assign different table name with adding foreign key. for e.g
I have a model like
class MyPost < ActiveRecord::Base
has_many :comments, class_name: PostComment
end
class PostComment < ActiveRecord::Base
belongs_to :post, class_name: MyPost
end
Now i want to change my migration file like this:
class CreatePostComments < ActiveRecord::Migration
def change
create_table :post_comments do |t|
t.belongs_to :post, index: true
t.timestamps null: false
end
add_foreign_key :post, :class_name => MyPost
end
end
But it is not working. Migration is getting cancelled. How do I change my migration file to work with my model structure.
You can pass in options for the foreign key as following:
class CreatePostComments < ActiveRecord::Migration
def change
create_table :post_comments do |t|
t.references :post, foreign_key: { to_table: :my_posts }, index: true
t.timestamps null: false
end
end
end
This is also true for the index option if you like to add a unique constraint:
t.references :post, foreign_key: { to_table: :my_posts }, index: { unique: true}
By the way, references is an alias for belongs_to, or to be more exact, belongs_to is an alias for references.
See the details in the implementation rails 5.0.rc2 & rails 4.2
It should look like this:
class CreatePostComments < ActiveRecord::Migration
def change
create_table :post_comments do |t|
t.belongs_to :post, index: true
t.timestamps null: false
end
add_foreign_key :post_comments, :my_posts, column: :post_id
end
end
Take a look at the documentation: http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/add_foreign_key
You use the column option when the column is named differently.

Resources