Rails: foreign keys - how does the DB table looks like? - ruby-on-rails

i have found this example here on stack overflow:
class User < ActiveRecord::Base
has_many :winners, class_name: "Competition", foreign_key: "competition_id"
end
class Competition < ActiveRecord::Base
belongs_to :winner, class_name: "User", foreign_key: "winner_id"
end
What exactly is the "has_many: winners" or "belongs_to: winner" in this case?
Is there a "competition_id" column in the users table and a "winner_id" column in the competitions table?
Greets

The tables would look something like:
users:
id: integer,
# other fields - name, email etc.
competitions:
id: integer,
winner_id: integer,
# other fields - name, place etc.
When someone uses the winners has_many association, for example like:
User.find(3).winners
it'll do a query that ends up as something like:
SELECT * FROM competitions WHERE winner_id = 3 # the 3 here comes from the user's id
When someone uses the winner belongs_to association, for example like:
Competition.find(4).winner
it'll do a query that ends up as something like:
SELECT * FROM users WHERE id = 1 # the 1 here has come from the winner_id on the competition record
The winners association is possibly poorly named; won_competitions would likely be a better name.
More reading: https://guides.rubyonrails.org/association_basics.html

Related

accepts_nested_attributes_for is contradicting the relational database logic in Rails

Ruby on Rails model logic conflicts with the relational database logic.
In Ruby on Rails, the model possessing the belongs_to, will have the foreign key in the database. So, the model:
class Student < ApplicationRecord
belongs_to :university
end
class University < ApplicationRecord
has_many :university
end
Will have the migration file:
class CreateStudents < ActiveRecord::Migration[6.0]
t.string :name
t.references :university, foreign_key: true
end
class CreateUniversities < ActiveRecord::Migration[6.0]
t.string :name
end
And, logically, the student will have the University foreign_key in the database, like so:
Student
-id int (PK)
-name varchar
-University_id int (FK)
University
-id int (PK)
-name varchar
So good so far
The problem arises when I want accepts_nested_attributes_for in the student model for referencing a University in the _form.html.erb. In order to use it, the host model, in this case Student, must have a has_one instead of a belongs_to for the referencing table. So it would become:
class Student < ApplicationRecord
has_one :university
accepts_nested_attributes_for :university
end
Notice how, in order to use accepts_nested_attributes_for for referencing the university, the Student model MUST replace its belongs_to :university for a has_one :university, and University would have a belongs_to :student, instead of a has_many :student.
So in the migration file, Student would lose its reference to University, and the latter would have a reference to Student instead, like so:
class CreateStudents < ActiveRecord::Migration[6.0]
t.string :name
end
class CreateUniversities < ActiveRecord::Migration[6.0]
t.string :name
t.references :student, foreign_key: true
end
And the database would forcefully be:
Student
-id int (PK)
-name varchar
University
-id int (PK)
-name varchar
-Student_id int (FK)
Which is wrong, because there should be a one to many relationship from University to Student, not the other way around.
So, is there a way to use accepts_nested_attributes_for, without messing up the relational database logic by having to forcefully inverse the relations in the models ?
accepts_nested_attributes_forNested attributes allow you to save attributes on associated records through the parent.
So you might be having a slight mix up about which one you want to be the parent. If you make the student the parent with nested attributes for the university you would not be referencing the university but essentially creating a new university record or updating an existing one with the nested attributes you used in your student form.
One-to-one
Consider a Member model that has one Avatar:
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar
end
Enabling nested attributes on a one-to-one association allows you to create the member and avatar in one go:
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'
So you would only be updating/creating new University records for each student you used those nested attributes on.
Here is for one-to-many:
One-to-many
Consider a member that has a number of posts:
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
You can now set or update attributes on the associated posts through an attribute hash for a member: include the key :posts_attributes with an array of hashes of post attributes as a value.
For each hash that does not have an id key a new record will be instantiated, unless the hash also contains a _destroy key that evaluates to true.
params = { member: {
name: 'joe', posts_attributes: [
{ title: 'Kari, the awesome Ruby documentation browser!' },
{ title: 'The egalitarian assumption of the modern citizen' },
{ title: '', _destroy: '1' } # this will be ignored
]
}}
member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
You can also see that in the one to many example above. Are you looking to change/create a University record when you post a Student record?
If you are just looking for a reference you essentially have that in your first example and could just change your routes up a bit to have the student referenced by the university.
Then once you have the routes nested you can just do a little work to the form partial. Here is a guy doing just that
So maybe if I am way off here describe why when you create/update a Student you want to create/update a University. If that is what you want to do.
This would helps others with your context a little more and might help others understand your intent with the nested attributes.
Add your form partial for example and explain your goal.
EDIT:
As a punt you can maybe look at has and belongs to many association.
This tutorial talks about nested attributes with a many to many association. But since I'm not certain exactly what your after it may or may not help.
Another punt sounds like you want maybe a student's university record to change when you update them. So you would have the student belong to the university and the university to has many students but also has many university records and the student to has many/has one university record and the record to belong to both.
aka:
this
Then you could have the student have accepts nested attributes for the university record/s.
I see that I have REALLY made a blunder trying to explain my doubts (it is my first question, I have learned from my mistake).
The ultimate goal was to know:
Can I use accepts_nested_attributes_for when there is a belongs_to instead of a has_one or has_many ?
So, the answer is yes:
class Student < ApplicationRecord
belongs_to :university
accepts_nested_attributes_for :university
end
In the student controller, there should be a build_:
def new
#student = Student.new
#student.build_university
end
I REALLY, REALLY, REALLY mistyped my question, and I apologize for it.
Thanks for all your answers.

Multiple relationships between two models in rails

I have a comment system with two tables: comments, and users. On the comment I want to record who the author was and also I want to notify any user that is mentioned in the comment with (#username). So I'm thinking I need to have an author_id on the comment, and also a comments_users table with the comment id and all the users ids that were mentioned. Would this be a correct way to accomplish it?:
User:
has_many :comments
Comment:
belongs_to :users, class_name: 'User', foreign_key: 'author_id'
has_many :users
The associations could be set up thus:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :mentions, join_table: "comments_users", association_foriegn_key: "comment_id"
end
Class Comment < ActiveRecord::Base
belongs_to :author, class_name: "User", foreign_key: "author_id"
has_and_belongs_to_many :mentions, join_table: "comments_users", foreign_key: "comment_id"
end
#comments_users
comment_id | user_id
This will allow you to call:
#user.comments #-> shows comments user has authored
#user.mentions.first.comment #-> shows first comment user was mentioned in
#comment.author #-> shows user who authored comment
#comment.mentions.first.user #-> shows first user who was mentioned in comment
Update
HABTM still needs a table (Rails migration for has_and_belongs_to_many join table), but the difference is that it doesn't need a primary key column (just comment_id | user_id)
We've created a "self-referential" habtm relationship, meaning you don't need to "create" any records -- they should all be created already. The HABTM will just reference them. As such, you'll need to use the << ActiveRecord method to add records into your mentions collection:
#app/controllers/comments_controller.rb
Class CommentsController < ActiveRecord::Base
def create
#comment = Comment.new(comments_params)
#comment.save_with_mentions
end
end
#app/models/comment.rb
Class Comment < ActiveRecord::Base
def save_with_mentions
comment = self.save
#do something to find mentioned users
mentioned_users = User.where("user_id = 1") #example
for user in mentioned_users do
comment.mentions << user
end
end
end
There are always many ways to accomplish any given task, but I'm guessing you're looking for something like this for your models & associations.
User:
has_many :comments
The user model association looked right.
Comment:
belongs_to :author, class_name: 'User', foreign_key: 'user_id'
has_many :users
Note, the belongs_to should reference a model in singular-naming style (ie: user vs users). I think you're going to want to do a reference like comment.author to find the author of your comments. It is more typical to provide a foreign_key of user_id when referring to a User model to keep things clear, but then provide a clarifying association name like "author" or "creator" or whatever for reference as I showed above. So your Comments table would have a foreign_key of user_id to reference back to the Users table. This user would be referenced in Rails by the name "author".
The second part of your question that has to do with tracking other user references in your model sounds like a one-to-many from the comment-users table. So, that sounds like one option. Similar to your "author" comment, you may want to provide a clearer name like "tags" which can just be references to users.
Another good option for this feature may be to set up a polymorphic table (essentially a flexible join table) if you plan to use this principle elsewhere in your app (like for referencing/tagging people in other elements like a photo or posting or something). It could provide greater flexibility for adding features and tracking these user references. A polymorphic table could have any name, but usually has an "-able" type name - like "taggable". Here's a useful reference: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

rails - find joining multiple fields under different names

I have these two tables
tickets - id, user_id, manager_id etc
users - id, first_name etc
user_id and manager_id are both references for users
using rails is there a way of creating a find that pulls in the users info and managers(users) info.
The sql joins would look abit like this -
LEFT JOIN users ON users.id = tickets.user_id
LEFT JOIN users as managers ON managers.id = tickets.manager_id
Expected result
ticket:
subject: something interesting
user_id: 1
manager_id: 3
user:
id: 1
name: Frank
manager:
id: 3
name: Alex
Thanks, Alex
If I understood you correctly, you should use something like:
class Ticket < ActiveRecord::Base
belongs_to :user
belongs_to :manager, :class_name => "User"
end
class Ticket < ActiveRecord::Base
has_many :tickets
end
Then you can use
#ticket = Ticket.first
#ticket.user.name >> "Frank"
#ticket.manager.name >> "Alex"
Your models should be set up as this :
#ticket.rb
belongs_to :user
belongs_to :manager
#manager.rb
has_many :tickets
#user.rb
has_many :tickets
Examples of finds (still not sure how you want to set up your find ):
Ticket.find(x)
Ticket.all.select{|t|t.manager_id == #manager.id}
Ticket.find(:all, :conditions => 'manager_id="#manager.id"')

Rails 3, belongs_to, has one? For 3 models, Users, Instances, Books

I have the following models:
Users (id, name, email, instance_id, etc...)
Instances (id, domain name)
Books (id, name, user_id, instance_id)
In Rails 3, When a new book is created, I need the user_id, and instance_id to be populated based on the current_user.
Currently, user_id is being assigned when I create a new book but not instance_id? What needs to happen in rails to make that field get filled out on book creation?
Also, shouldn't rails be error'ing given that I can create books without that instance_id filled out?
thxs
It looks like you have de-normalized User and Book models by adding reference to Instance model. You can avoid the redundant reference unless you have a specific reason.
I would rewrite your models as follows:
class Instance < ActiveRecord::Base
has_many :users
has_many :books, :through => :users, :order => "created_at DESC"
end
class User < ActiveRecord::Base
belongs_to :instance
has_many :books, :order => "created_at DESC"
end
class Book < ActiveRecord::Base
belongs_to :user
has_one :instance, :through => :user
end
Now to create a new book for a user.
current_user.books.build(...)
To get a list of the books belonging to user's instance:
current_user.instance.books
To get a list of the books created by the user:
current_user.books
Make sure you index the instance_id column in users table and user_id column in books table.
Rails will only produce an error in this case if (a) you have a validation that's failing, or (b) you have database foreign keys that aren't being satisfied.
What's an instance? i.e. if instance_id is to be populated based on the current user, what attribute of the user should supply it? (the instance_id? why?)

rails models - two tables have the same primary and foreign key fields

I am using an existing database with a rails app.
I can't change the table or column names.
Lets say table one is "invoices" and table 2 is "orders"
they both have a primary key that is called the same thing, lets say "order_id"
invoices can find its order by looking at the primary key "order_id" in the orders table.
Vice versa for an order finding its invoice.
Invoices can have but not always have one order (you might be invoiced for something besides an order, like a "work_order" which would be found by looking for the "order_id" in the primary key position of the "work_orders" table. So invoices might have a work_order or an order.
orders always has an invoice
work_orders always has an invoice
Im trying to figure out the classes in the models.
Do you set the primary and foreign keys to the same thing? What about belongs_to? The way this DB is set up, nothing really belongs to anything, they just reference each other by this same value "order_id". Would it be like this?
class Invoice < ActiveRecord::Base
set_primary_key(:order_id)
set_foreign_key(:order_id)
end
-- snip --
class Order < ActiveRecord::Base
set_primary_key(:order_id)
set_foreign_key(:order_id)
end
... And the same for a work order.
class WorkOrder < ActiveRecord::Base
set_primary_key(:order_id)
set_foreign_key(:order_id)
end
Is this correct? It seems to trashy of a way to do it but this DB is terrible.
What about all the belongs_to stuff?
Let me know if I have left anything out.
Thanks!
I believe the answer could be:
class Order < ActiveRecord::Base
set_primary_key(:order_id)
belongs_to :invoice, :foreign_key => :order_id
end
class WorkOrder < ActiveRecord::Base
set_primary_key(:order_id)
belongs_to :invoice, :foreign_key => :order_id
end
class Invoice < ActiveRecord::Base
set_primary_key(:order_id)
has_one :work_order
has_one :order
end
Although I'm not really sure your primary key can also be a foreign key (I'm new to Rails too)

Resources