Approaching custom foreign keys - sql

I have the following tables
User.
Name
Email
..
password_digest
Company.
Name
Address
...
contact_person_id
So my company have a contact_person, which take a user_id, therefor being a foreign key.
I have tried the following.
class Company < ActiveRecord::Base
has_one(:user, foreign_key: 'contact_person_id')
end
class User < ActiveRecord::Base
belongs_to(:company, :class_name => "Company", :foreign_key => 'contact_person_id')
end
But when i try to run my (respond_to) rspecs i get
Failure/Error: before { #company = FactoryGirl.create(:company) }
ActiveModel::MissingAttributeError: can't write unknown attribute `contact_person_id'
My Factory
FactoryGirl.define do
factory :company do
name "Starup Company"
address "Test street 37"
zip 2200
website "http://example.com"
industry "Construction"
contact_person user
end
end
What am i doing wrong? And how would i point to user with a company object?
company.contact_person

2 issues - you have belongs_to and has_one reversed and you are calling contact_person method on a company, but you defined the relationship to be as a user
if you want to use the contact_person method on the company, try this:
class Company < ActiveRecord::Base
belongs_to(:contact_person, class_name: 'User')
end
class User < ActiveRecord::Base
has_one(:company, :foreign_key => 'contact_person_id')
end
This page has a good overview: http://guides.rubyonrails.org/association_basics.html#choosing-between-belongs-to-and-has-one The foreign key ALWAYS is found in the table with the belongs_to association.
The way you wrote it each user can only have one company that they are the contact person for.
To write the rails code the way you have it, you'd have to have the foreign key in the users table.

Related

Rails how to find records where the has_many relation exists?

In the below association, I want to collect all Users who don't have any projects->
class User < ActiveRecord::Base
has_many :projects, :foreign_key => :user_id
end
class Projects < ActiveRecord::Base
belongs_to :user, :foreign_key => "user_id"
end
From User model, how can I get all the users that do not have any projects? I tried using includes and join but didn't get the expected result
You could try:
User.where.not(id: Project.pluck(:user_id).uniq)
Breaking it down:
Project.pluck(:user_id).uniq
will give you an array of user_ids from your projects. Essentially, users with projects.
Then:
User.where.not(id: Project.pluck(:user_id).uniq)
returns users who have an id that is not in the array of users with projects.

Conditionally Saving has_many_through Relationships

In Rails, how would one conditionally associated records on a has_many_through relationship? Using the following Rails docs example:
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
Suppose I wanted to have an appointment reference exactly two physicians. That is, there will not be any appointment record one there is less than two physicians assigned. However, how could that appointment then reference each physician?
Example
Basically, I want to keep track of users liking other users and mutual likes between them. A connection is established when both users like each other. But I don't want a connection when only one user likes another but it is not reciprocal.
When User A likes User B. A "like" is created.
When User B likes User A. A "like" is created. A "connection" is also created.
The connection should be able to call:
connection.users
The user should be able to call:
user.likes
user.connections
The problem that I'm having is how can that relationship table know when it is mutual?
For the original question, a connection doesnt make a difference between the two users, so i would model it as a one to many relationship and validate it only has two users.
A like has two users, the liker (giver of the like) and the likee (receiver of the like). Every time you create new like, you should check if the likee also likes the liker. If likee.likes.where(likee: liker)? If yes, then create the new connection with both users.
class User < ApplicationRecord
has_many :likes
has_many :connections
end
class Like < ApplicationRecord
belongs_to :user, :foreign_key => 'liker_id'
belongs_to :user, :foreign_key => 'likee_id'
end
class Connection < ApplicationRecord
has_many :likes
has_many :users, through: likes
end
I want to add that i am not 100% sure of this as i am currently learning Rails myself. But this is what I came up with and hopefully its useful (and correct).

Check if record exist, if yes update else create new - nested forms - rails

I'm using rails 3.2. I have a nested form for "Samples". A "Patient" can have several "Samples" but a "Sample" can only have one "Patient".
Patient primary key is "id" but is has also 4 foreign keys: nid, province_id, district_id and facility_id.
Here's the models (i'm trying to show only the relevant code):
Patient.rb
class Patient < ActiveRecord::Base
attr_accessible :date_of_birth, :infant_name, :nid, :province_id, :district_id, :facility_id, :gender_id, :gender_atributes
belongs_to :gender
belongs_to :province
belongs_to :district
belongs_to :facility
has_many :samples
accepts_nested_attributes_for :gender
validates :province_id, presence: true
validates :district_id, presence: true
validates :facility_id, presence: true
validates :nid, presence: true
#To validate uniqueness of patient, that is, one with unique nid, province, district and facility
validates_uniqueness_of :nid, :scope => [:province_id, :district_id, :facility_id]
end
Sample.rb
class Sample < ActiveRecord::Base
attr_accessible :dateOfSample, :nrOrdem, :patient_id,:facility_id, :province_id, :district_id
belongs_to :facility
belongs_to :patient
belongs_to :province
belongs_to :district
accepts_nested_attributes_for :patient
end
What I want is to avoid corrupted data on pacient, that is, to have different patients with the same (nid, province_id, district_id and facility_id).
Right now the nested form for sample lets me create a new patient and if I try to create a new one with the same (nid, province_id, district_id and facility_id) I get an error saying that the "nid is already taken".
But, as a Patient can have different samples, I want to (while trying to create the record) check if the patient already exists, if it does than adds "patient_id" to the "sample" record, otherwise creates a new one.
It would be great to also show a message saying that the patient already exists and fill the corresponding data on the form, but for now I'll be glad if it adds the patient_id to the sample record, ignoring the rest of the patient fields in the nested form.
I tried to implement something like this
rails: create Parent, if doesn't exist, whilte creating child record but it didn't work.
How can I solve this?
EDIT
Here's how the code ended up after #James Mason help.
On Sample.rb
def patient_attributes=(attrs)
self.patient = Patient.where({:nid => attrs[:nid], province_id: province_id, district_id: district_id, facility_id: facility_id}).first_or_initialize(attrs)
end
You're looking for first_or_create and/or first_or_initialize. In this case, since you care whether the record already existed, you probably want the initialize version.
#patient = Patient.where({nid: nid, province_id: province_id, district_id: district_id, facility_id: facility_id}).first_or_initialize
#show_exists_message = !#patient.new_record?
#patient.save
You can either put this code in your controller's create action, or, since you're using nested attributes, define a custom Sample#patient_attributes= method:
class Sample < ActiveRecord::Base
belongs_to :patient
accepts_nested_attributes_for :patient
def patient_attributes=(attrs)
self.patient = Patient.where(attrs).first_or_initialize(attrs)
#show_exists_message = !#patient.new_record?
end
end

has_many :through and build

I have three models, Account, User and Contact:
class User < ActiveRecord::Base
has_one :account
has_many :contacts, :through => :account
end
class Account < ActiveRecord::Base
belongs_to :owner, :class_name => 'User'
has_many :contacts
end
class Contact < ActiveRecord::Base
belongs_to :account
end
I'm trying to scope build a new contact through the user record, like this in my contacts controller.
def create
#contact = current_user.contacts.build(params[:contact])
respond_to do |format|
if #contact.save
...
else
...
end
end
end
When I do this, I don't receive any errors, the contact record is saved to the database however the account_id column is not set on the contact, and it is not added to the collection so calling #current_user.contacts returns an empty collection.
Any suggestions?
Using build makes a new instance of Contact in memory, but you would need to manually set the account_id on the record (e.g. #contact.account_id = current_user.account.id), or perhaps set it in a hidden field in the new form used to display the contact for creation such that it is picked up in the params array passed to the build method.
You might also want to consider whether accepts_nested_attributes_for may be helpful in this case. Another option may be to use delegate, although in both cases, your use may be sort of the opposite of what these are intended for (typically defined on the "parent").
Update:
In your case, the build method is added to both the User instance and to the Account (maybe "Owner") instance, because you have both a many-to-many relationship between User and Contact, as well as a one-to-many relationship between Account and Contact. So to get the account_id I think you would need to call Account's build, like
#contact = current_user.accounts.contacts.build(params[:contact])
Does this work?

Rails - two foreign keys on one model both refer to same model

I'm fairly new to ActiveRecord associations. I'm sketching out an application that tracks who owes each other money among a set of users. An Expense model and a User model seem like natural choices, I'm just not sure how to define the relationship between the two. For example, I want to track the creditor ("owner") and the debtor of each expense, but that's really just two foreign keys that go back to User. In addition, each user can have multiple expenses (both as creditor and debtor) My best guess for the associations thus far is something like:
class Expense
# belongs_to or has_one here?
# Not sure about class => User syntax:
# need to alias to foreign keys that reference the same model
belongs_to :creditor, :class => User
belongs_to :debtor, :class => User
class User
# has_many expenses defines a creditor relationship (user owns expense)
# how to define debtor relationship? (belongs_to...?)
has_and_belongs_to_many :expenses
I've read the Rails guide on associations but I'm still fairly lost on foreign keys and join tables. Any input is much appreciated!
So this is definately not a has_and_belongs_to_many thats for many-to-many relationships. You just need to use a couple has_many relationships. I think it should end up looking like this:
Edit: oops I fudged that a bit that up sorry let me have another go:
class Expense
# make sure expense table has 'creditor_id' and 'debtor_id' rows
belongs_to :creditor, :class_name => "User", :foreign_key => :creditor_id
belongs_to :debtor, :class_name => "User", :foreign_key => :debtor_id
class User
has_many :debts, :class_name => "Expense", :foreign_key => :debtor_id
has_many :credits, :class_name => "Expense", :foreign_key => :creditor_id
The other answers tell you what you need to do, but it can be kind of confusing to people who are new to Rails, as I am, to piece all these things together, so here is a complete solution, including both Migrations and Models.
Also, as a side note: I prefer Loans, Lender and Borrower to Expense, Creditor and Debtor, or Debt, Creditor and Debtor. Mostly because Expense is ambiguous and Debt is too similar to Debtor. But it's not that important; just do what makes sense to you, since you will be maintaing your code.
Migrations
class CreateLoans < ActiveRecord::Migration
create_table :loans do |t|
def up
t.references :lender
t.references :borrower
end
end
end
Here you are specifying that there are two columns in this table that will be referred to as :lender and :borrower and which hold references to another table. Rails will actually create columns called 'lender_id' and 'borrower_id' for you. In our case they will each reference rows in the Users table, but we specify that in the models, not in the migrations.
Models
class Loan < ActiveRecord::Base
belongs_to :lender, class_name => 'User'
belongs_to :borrower, class_name => 'User'
end
Here you are creating a property on the Loan model named :lender, then specifying that this property is related to the User class. Rails, seeing the 'belongs_to', will look for a column in the loans table called 'lender_id', which we defined above, and use that to store the foreign key. Then you're doing the exact same thing for the borrower.
This will allow you to access your Lender and Borrower, both instances of the User model, through an instance of the Loan model, like this:
#loan.lender # Returns an instance of the User model
#loan.borrower.first_name # Returns a string, as you would expect
As a side note: the 'belongs_to' nomenclature makes decent sense in this case, but can be kind of confusing elsewhere. Just remember that it is always used on whichever thing contains the foreign key.
class User < ActiveRecord::Base
has_many :loans_as_lender, :class_name => 'Loan', :foreign_key => 'lender_id'
has_many :loans_as_borrower, :class_name => 'Loan', :foreign_key => 'borrower_id'
end
Here you are creating a property on the User model named :loans_as_lender, specifying that this property is related to the Loan model, and that the foreign key on the Loan model which relates it to this property is called 'lender_id'. Then you are doing the same thing for :loans_as_borrower.
This allows you to get all the loans where a User is the lender or borrower, like this:
#address.loans_as_lender
#address.loans_as_borrower
Doing either of these will return an array of instances of the Loan model.
If your expense migration looks like this:
create_table :expenses do |t|
t.integer :creditor_id, :null => false
t.integer :debtor_id, :null => false
# other attributes here
end
then your Expense model is sufficient. If you take a look at the documentation for belongs_to, you'll see that it will correctly infer the foreign keys into the user table:
:foreign_key
Specify the foreign key used for the association. By default this is guessed to be the name of the association with an “_id” suffix. So
a class that defines a belongs_to :person association will use
“person_id” as the default :foreign_key. Similarly, belongs_to
:favorite_person, :class_name => "Person" will use a foreign key of
“favorite_person_id”.
So you don't need to explicitly specify a foreign key here. If you use other naming conventions for the ids in your expenses model, then you need to explicitly specify them in your associations.
For your User model, you don't have a many_to_many relationship with expenses - an expense always belongs to exactly one debtor and exactly one creditor. So all you need is two has_many associations:
has_many :debts, :class_name => 'Expense', :foreign_key => :debtor_id
has_many :credits :class_name => 'Expense', :foregin_key => :creditor_id