I have the following:
class User < ActiveRecord::Base
has_one :car, :class_name => 'Car', :foreign_key => 'user_id'
class Car < ActiveRecord::Base
belongs_to :worker, :class_name => 'User', :foreign_key => 'user_id'
It is basically a one-to-one relationship between a user and a car.
What I want is for the User to be able to have one and only one car. That implies the fact that if he creates a car assigned to him, he won't be able to create the second.
How could this be done?
There are certainly a couple different ways of accomplishing this. I would suggest creating a composite key index on that table to ensure that the user_id is unique in the table. This will ensure that it will only be present once. In a migration, you could write something like this.
add_index(:cars, :worker_id, :unique => true)
The first argument is the table name (don't forget this is generally the pluralized version of the class name). The field name comes second. The unique true is what will prevent you from inserting an extra row.
Note: This is a database level constraint. If you hit this because validations didn't catch it, it will throw an error.
In addition to this solution, you will want to add a validation to the Car model itself.
validates_uniqueness_of :worker_id, message: "can not have more than one car"
You'll see this error come through with something like "Worker ID can not have more than one car". You will most likely want to customize the "Worker ID" section of this. Refer to this post for instructions on how to do that.
You certainly don't have to do the db constraint, but in case anyone else inserts into the DB, it's a good idea. Otherwise, you'll have "invalid" data as far as Rails is concerned.
Change the definition of the relationship slightly:
class User < ActiveRecord::Base
has_one :car
class Car < ActiveRecord::Base
belongs_to :worker, :class_name => 'User', :foreign_key => 'user_id'
And you'll establish what you're looking for. See: http://guides.rubyonrails.org/association_basics.html#the-has-one-association
why don't you just test before the user tries to add a car?
if worker.car
raise "sorry buddy, no car for you"
else
car = Car.create(user_id: worker.id)
end
Related
My Seminar model has a column named teacher_id. That part of the relationship is working. I can call seminar.teacher to retrieve the User who teaches that seminar.
I'd like to be able to invert that query. In other words, I need to call teacher.own_seminars to get a collection of all the seminars where that User is listed as the teacher_id. I know that I could call Seminar.where(:teacher => teacher), but that's clunky. And I think that the performance is probably worse that way.
Note: Some of the Users are students who are linked to Seminar through the seminar_user join table, but I don't think that affects this question.
This is the model setup that isn't quite working yet
class User < ApplicationRecord
has_many :own_seminars, :class_name => "Seminar", foreign_key: 'own_seminar_ids'
end
class Seminar < ApplicationRecord
belongs_to :teacher, class_name: "User", foreign_key: 'teacher_id'
end
Cheers!
In foreign_key option, you specify the column which is, well, the foreign key.
The way has_many works, is it tries to guess, which one of the fields in the referenced entity corresponds to the primary key of this entity. By default, it's user_id (derived from name User). But since your column is actually called teacher_id, you should use that instead.
has_many :own_seminars,
class_name: "Seminar",
foreign_key: 'teacher_id'
I have the models:
class Idea < ActiveRecord::Base
has_many :connections, :class_name => 'IdeaConnection', :foreign_key => 'idea_a_id', :dependent => :destroy
has_many :ideas, :through => :connections, :source => :idea_b, :dependent => :destroy
end
class IdeaConnection < ActiveRecord::Base
belongs_to :idea
belongs_to :idea_a, :class_name => 'Idea'
belongs_to :idea_b, :class_name => 'Idea'
belongs_to :relationship
end
class Relationship < ActiveRecord::Base
has_many :idea_connections
end
Idea, as you can see, own itself through Connections (join table). Each Connection entry belongs to Relationship. What I'm trying to do is to, after adding an Idea to another with:
Idea.find(1).ideas << Idea.find(2)
which is working and saving properly, get its connection on join table and update its relationship:
Ex:
Idea.find(1).connections.find_by_idea_b_id(Idea.f
ind(2).id).relationship = Relationship.find(1)
It processes correctly but it won't save.
Please, help, what am I missing?
ps: I don't want to do it by manually editting the relationship_id since it's ugly.
ps2: Before you answer, remember the fact that autosave:true do not work for belongs_to/has_many relationships.
You're working with connection object. Remember it.
Your problem is that you call find_by... method in has_many association. It returns one record BUT Idea model has no ruby attribute link to that. See here why. That's why Idea#save cannot call IdeaConnection#save (remember that for cascade saving connections realtion must have autosave: true if connection already exists).
So I suggest you two options:
Set :inverse_of on Idea#connections and IdeaConnection#idea_a relations and preload all records before mangling with Idea.find(1).connections.find_by_idea_b_id(Idea.f
ind(2).id).relationship = Relationship.find(1). But I don't recommend you to do so because:
As I said you're working with connection object. Just do so:
Idea.find(1).connections.create do |connection|
connection.idea_b = Idea.find(2)
connection.relationship = Relationship.first
end
This line of code won't save it
Idea.find(1).connections.first.relationship = Relationship.first
What you need to do is:
Method #1: ( add the connection to the has_many relation )
Relationship.first.idea_connections << Idea.find(1).connections.first
OR
Method #2: ( add relationship_id to the connection then manually save it)
connection = Idea.find(1).connections.first
connection.relationship_id = Relationship.first.id
connection.save
Assignments on the relational object level don't/won't automatically save; you have to tell them to. In ActiveRecord the push method (<<) has save built into it, which is why that was working for you. Setting a value (=) however does not have save built in, so you have to do it manually.
If you're interested, here's a link to another SO question where an answer talks about why push saves: Rails push into array saves object
I'm having an issue with several many-to-many relations in my Rails project. It can be illustrated with an example:
Say I have the models Person and PhoneNumber, joined by PersonPhoneNumber. The relation is many-to-many because people can have more than one phone number, and more than one person can be reached at the same phone number (in a case such as a help desk).
class Person < ActiveRecord::Base
has_many :person_phone_numbers
has_many :phone_numbers, :through => :person_phone_numbers
end
class PhoneNumber < ActiveRecord::Base
has_many :person_phone_numbers
has_many :people, :through => :person_phone_numbers
validates :number, :uniqueness => true
end
class PersonPhoneNumber < ActiveRecord::Base
belongs_to :person
belongs_to :phone_number
end
I have a person form that lets me create/update people's contact information. I use it to assign the number 555-555-1212 to Bob. If a PhoneNumber object with that number doesn't exist, I want it to be created (as in the standard accepts_nested_attributes_for behavior). But if it does exist, I want to just create a PersonPhoneNumber object to associate Bob with that PhoneNumber.
How can I accomplish this most elegantly? I tried putting a before_validation hook in PersonPhoneNumber to look for a matching PhoneNumber and set phone_number_id, but this caused really bizarre behavior (including making my Rails server crash with the message Illegal instruction: 4).
You can use exists? method to check for existence first, like this:
#person.phone_numbers.build(number: "555-555-1212") unless #person.phone_numbers.exists(number: "555-555-1212")
Or you can do something like this:
PhoneNumber.find_or_create(person_id: #person.id, number: "555-555-1212")
Rachel the Rails documentation says this:
A has_and_belongs_to_many association creates a direct many-to-many connection with another model, with no intervening model.
What is the difference?
I have Accounts and AccountAddressess. An account can have many AccountAddressess and I would like to specify one as the "default_account_address", so in the Account table, I have a column named "default_account_address_id". Here is the current state of the models.
class Account < ActiveRecord::Base
has_many :account_addresses
belongs_to :default_account_address,
:class_name => "AccountAddress",
:inverse_of => :account_assigned_to_as_default
end
class AccountAddress < ActiveRecord::Base
belongs_to :accounts
has_one :account_assigned_to_as_default,
:class_name => "Account",
:foreign_key => :default_account_address_id,
:inverse_of => :default_account_address
end
This works fine except for the fact that #account.default_account_address returns an account address and #account.account_addresses returns an empty array.
So, the issue is that the default account address is not included in #account.account_addresses.
Any ideas on the best way to approach this issue? I considered habtm, but it doesn't seem appropriate. I considered using has_one :default_account_address, but this doesn't make sense because the default_account_address_id column is on the account table. Thanks.
There is probably a better way, but here is something that came to mind:
class Account < ActiveRecord::Base
has_many :account_addresses
def default_address
account_addresses.find_by_default true
end
end
class AccountAddress < ActiveRecord::Base
belongs_to :accounts
end
This of course assumes you have a boolean column named default in AccountAddress. I would probably add validation to AccountAddress that would check that there is only 1 AccountAddress marked as default for a given account_id. You could also create a method in AccountAddress that not only marks an address as default, but also unmarks all associated addresses for you.
Like I said, there is probably something better out there, but this should allow the default address to also show in #account.account_addresses.
Another solution:
class Account < ActiveRecord::Base
has_many :account_addresses
has_one :default_account_address, -> { find_by_default true },
class_name: 'AccountAddress'
end
class AccountAddress < ActiveRecord::Base
belongs_to :accounts
end
I don't know if this is best practice, however, I would just add an attribute "mainaddress" to the table and use just has_many/belongs_to in the relations. Into the account model I would put a function that fetches the main address using a simple query where mainaddress is true.
Another idea is to use https://github.com/swanandp/acts_as_list and then treat default address as the top one, then, setting some address as default would be as simple as:
address.move_to_top if params[:set_as_default]
Especially if all you need is to show it the first in some list or combo box.
Querying default address is also easy, since it always first in the scope of "position".
This gem set_as_primary solves the same issue but in a different way.
I have the following setup:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person', :join_table => 'authors_publications'
has_and_belongs_to_many :editors, :class_name=>'Person', :join_table => 'editors_publications'
end
class Person < ActiveRecord::Base
has_and_belongs_to_many :publications
end
With this setup I can do stuff like Publication.first.authors. But if I want to list all publications in which a person is involved Person.first.publications, an error about a missing join table people_publications it thrown. How could I fix that?
Should I maybe switch to separate models for authors and editors? It would however introduce some redundancy to the database, since a person can be an author of one publication and an editor of another.
The other end of your associations should probably be called something like authored_publications and edited_publications with an extra read-only publications accessor that returns the union of the two.
Otherwise, you'll run in to sticky situations if you try to do stuff like
person.publications << Publication.new
because you'll never know whether the person was an author or an editor. Not that this couldn't be solved differently by slightly changing your object model.
There's also hacks you can do in ActiveRecord to change the SQL queries or change the behavior of the association, but maybe just keep it simple?
I believe you should have another association on person model
class Person < ActiveRecord::Base
# I'm assuming you're using this names for your foreign keys
has_and_belongs_to_many :author_publications, :foreign_key => :author_id
has_and_belongs_to_many :editor_publications, :foreign_key => :editor_id
end