Ruby on rails - Reference the same model twice? - ruby-on-rails

Is it possible to set up a double relationship in activerecord models via the generate scaffold command?
For example, if I had a User model and a PrivateMessage model, the private_messages table would need to keep track of both the sender and recipient.
Obviously, for a single relationship I would just do this:
ruby script/generate scaffold pm title:string content:string user:references
Is there a similar way to set up two relations?
Also, is there anyway to set up aliases for the relations?
So rather than saying:
#message.user
You can use something like:
#message.sender or #message.recipient
Any advice would be greatly appreciated.
Thanks.

Here's a complete answer to this issue, in case people visiting this question are new to Ruby on Rails and having a hard time putting everything together (as I was when I first looked into this).
Some parts of the solution take place in your Migrations and some in your Models:
Migrations
class CreatePrivateMessages < ActiveRecord::Migration
def change
create_table :private_messages do |t|
t.references :sender
t.references :recipient
end
# Rails 5+ only: add foreign keys
add_foreign_key :private_messages, :users, column: :sender_id, primary_key: :id
add_foreign_key :private_messages, :users, column: :recipient_id, primary_key: :id
end
end
Here you are specifying that there are two columns in this table that will be referred to as :sender and :recipient and which hold references to another table. Rails will actually create columns called 'sender_id' and 'recipient_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 PrivateMessage < ActiveRecord::Base
belongs_to :sender, :class_name => 'User'
belongs_to :recipient, :class_name => 'User'
end
Here you are creating a property on the PrivateMessage model named :sender, then specifying that this property is related to the User class. Rails, seeing the "belongs_to :sender", will look for a column in your database called "sender_id", which we defined above, and use that to store the foreign key. Then you're doing the exact same thing for the recipient.
This will allow you to access your Sender and Recipient, both instances of the User model, through an instance of the PrivateMessage model, like this:
#private_message.sender.name
#private_message.recipient.email
Here is your User Model:
class User < ActiveRecord::Base
has_many :sent_private_messages, :class_name => 'PrivateMessage', :foreign_key => 'sender_id'
has_many :received_private_messages, :class_name => 'PrivateMessage', :foreign_key => 'recipient_id'
end
Here you are creating a property on the User Model named :sent_private_messages, specifying that this property is related to the PrivateMessage Model, and that the foreign key on the PrivateMessage model which relates it to this property is called 'sender_id'. Then you are doing the same thing for received private messages.
This allows you to get all of a users sent or received private messages by doing something like this:
#user.sent_private_messages
#user.received_private_messages
Doing either of these will return an array of instances of the PrivateMessage model.
....

Add this to your Model
belongs_to :sender, :class_name => "User"
belongs_to :recipient, :class_name => "User"
And you are able to call #message.sender and #message.recipient and both reference to the User model.
Instead of user:references in your generate command, you'd need sender:references and recipient:references

hi there
to have both side relation do as bellow in your both models:
class Message < ActiveRecord::Base
belongs_to :sender,
:class_name => "User",
:foreign_key => "sender_id"
belongs_to :recipient,
:class_name => "User",
:foreign_key => "recipient_id"
end
class User < ActiveRecord::Base
has_many :sent,
:class_name => "Message",
:foreign_key => "sent_id"
has_many :received,
:class_name => "Message",
:foreign_key => "received_id"
end
I hope this help you...

The above answers, while excellent, do not create foreign key constraints in the database, instead only creating indexes and bigint columns. To ensure that the foreign key constraint is enforced, add the following to your migration:
class CreatePrivateMessages < ActiveRecord::Migration[5.1]
def change
create_table :private_messages do |t|
t.references :sender
t.references :recipient
end
add_foreign_key :private_messages, :users, column: :sender_id, primary_key: :id
add_foreign_key :private_messages, :users, column: :recipient_id, primary_key: :id
end
end
This will ensure that the indices get created on the sender_id and recipient_id as well as the foreign key constraints in the database you're using.

Related

How to add a reference to primary key of same table twice in a table in postgres in rails5

I am using Rails 5.0.1 for web development and deploying my app on Heroku. I am using postgreSQL as my database.
I have a table called Users and a table called Friends.
In friends table I want to store the user Id twice, for example to indicate that user id 1 is friend of user id 2.
How can I do this?
So far I have tried following things with no success:
Attempt 1:
migration file:
create_table :friends do |t|
t.references :user, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
Model:
class Friend < ApplicationRecord
belongs_to :friend1, :foreign_key => :friend1_id, :class_name => 'User'
belongs_to :friend2, :foreign_key => :friend2_id, :class_name => 'User'
end
Attempt 2:
migration file:
create_table :friends do |t|
t.references :friend1_id, foreign_key: true
t.references :friend2_id, foreign_key: true
t.timestamps
end
Model:
class Friend < ApplicationRecord
belongs_to :friend1, :foreign_key => :user_id, :class_name => 'User'
belongs_to :friend2, :foreign_key => :user_id, :class_name => 'User'
end
Is there something wrong with the way I am modeling it? or, can this be done in a better/easier way?
My requirement is to store information about a user and his friends. A user can have multiple friends.
PS: Before deploying on Heroku/PostgreSQL, I was running the application on my local system using sqlite3, the scripts seemed to be working fine there, but they don't work on Heroku/PostgreSQL now.
I think you have mostly the right pieces, but need to recombine them eg this should work:
class Friend < ApplicationRecord
belongs_to :friend1, :foreign_key => :friend1_id, :class_name => 'User'
belongs_to :friend2, :foreign_key => :friend2_id, :class_name => 'User'
end
if your columns are friend_1/friend_2
create_table :friends do |t|
t.integer :friend1_id
t.integer :friend2_id
t.timestamps
end
You may need to manually add foreign-keys though, as I don't think rails supports the differently-named keys.
Note: these other two things you tried definitely won't work because Rails finds user_id to be ambiguous... or repeated.
I did a search in Github, and found this. Hope helpful:
app/models/user.rb
has_and_belongs_to_many :friends, join_table: :friends_users
has_many :outgoing_friend_requests, class_name: FriendRequest
has_many :incoming_friend_requests, class_name: FriendRequest,
foreign_key: :friend_id
app/models/friend.rb
class Friend < ActiveRecord::Base
self.table_name = :users
has_and_belongs_to_many :users, join_table: :friends_users
end
app/models/friend_request.rb
class FriendRequest < ActiveRecord::Base
belongs_to :user
belongs_to :friend
end
Here is the Repo Link

Trying to implement bidirectional self-referential association

(I'm translating the code as I write, so I apologize for any mistakes!)
I'm trying to implement a bidirectional self referential relation involving the Associates table and a join table Property. I tried following this post but something is still wrong.
The way it's supposed to work is, an associate can be the proprietor of zero or more associates, and consequently an associate may have zero or more proprietors (makes more sense in the context of the application).
So I created the Property model:
class CreateProperties < ActiveRecord::Migration
def change
create_table :properties do |t|
t.integer :proprietor_id
t.integer :property_id
t.timestamps null: false
end
end
end
So the table contains only the ids of one proprietor and one property, both associates, per entry.
Following the tutorial linked above, I came to this configuration:
Associate.rb:
...
has_many :properties
has_many :related_properties, :through => :properties
has_many :proprietors, :class_name => "Property", :foreign_key => "proprietor_id"
has_many :related_proprietors :through => :proprietors, :source => :associate
...
Property.rb:
belongs_to :associate
belongs_to :related_properties, :class_name => "Associate"
However when I try to use these relations (<% #associate.related_properties.each do |property| %>), I get this error:
PG::UndefinedColumn: ERROR: column properties.related_properties_id does not exist
LINE 1: ... INNER JOIN "propriedades" ON "associados"."id" = "proprieda...
^
: SELECT "associates".* FROM "associates" INNER JOIN "properties" ON "associates"."id" = "properties"."related_properties_id" WHERE "properties"."associate_id" = $1
Basically, the column names are wrong in the generated SQL: properties.related_properties_id should be properties.proprietor_id, and properties.associate_id should be properties.proprietor_id as well.
What have I done wrong, and how can I fix this code to get the correct relations?
You need to setup two seperate associations since the foreign key on Property depends on what the Associates role is.
class Associate
# defines relations where Associate is the "owning" party
has_many :properties_as_proprietor,
class_name: 'Property',
foreign_key: 'proprietor_id'
has_many :properties,
through: :properties_as_property,
source: :property # what to select on Property
# defines relations where Associate is the "owned" party
has_many :properties_as_property,
class_name: 'Property',
foreign_key: 'property_id'
has_many :proprietors,
through: :properties_as_proprietor,
source: :proprietor # what to select on Property
end
class Property
belongs_to :proprietor, class_name: 'Associate'
belongs_to :property, class_name: 'Associate'
end

Write a migration with reference to a model twice

I have a message model (Message) and this models as a userTo and userFrom, so two references to User. How can i write the migration? My user model is User.
Thank you
Here's a complete answer to this issue, in case people visiting this question are having a hard time putting everything together (as I was when I first looked into this).
Some parts of the answer take place in your Migrations and some in your Models:
Migrations
class CreateMessages < ActiveRecord::Migration
create_table :messages do |t|
def up
t.references :sender
t.references :recipient
end
end
end
Here you are specifying that there are two columns in this table that will be referred to as :sender and :recipient and which hold references to another table. Rails will actually create columns called 'sender_id' and 'recipient_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 Message < ActiveRecord::Base
belongs_to :sender, :class_name => 'User'
belongs_to :recipient, :class_name => 'User'
end
Here you are creating a property on the Message model named :sender, then specifying that this property will be referencing an instance of the User class. Rails, seeing the "belongs_to", will look for a column in your database called "sender_id", which we defined above, and use that to store the foreign key. Then you're doing the exact same thing for the recipient.
This will allow you to access your Sender and Recipient, both instances of the User model, through an instance of the Message model, like this:
#message.sender.name
#message.recipient.email
Here is your User Model:
class User < ActiveRecord::Base
has_many :sent_messages, :class_name => 'Message', :foreign_key => 'sender_id'
has_many :received_messages, :class_name => 'Message', :foreign_key => 'recipient_id'
end
Here you are creating a property on the User Model named :sent_messages, specifying that this property is related to the Message Model, and that the foreign key on the Message model which relates to this property is called 'sender_id'. Then you are doing the same thing for received messages.
This allows you to get all of a users sent or received messages by doing something like this:
#user.sent_messages
#user.received_messages
Doing either of these will return an array of instances of the Message model.
In the migration, create two different columns for each kind of user. For example:
add_column :messages, :sender_id, :integer
add_column :messages, :receiver_id, :integer
Then in the model, that's where the logic to map each column to the User class happens:
belongs_to :sender, :class_name => 'User'
belongs_to :receiver, :class_name => 'User'
Of course, use your own words for sender and receiver, but Rails will automatically associate sender to the sender_id column (and the same logic for receiver)
You will then be able to interact with both user user.sender and user.receiver.

Rails multiple has_one associations

I have multiple models with created_by and modified_by columns. This is what I have for a Deal Model.
class Deal
has_one :user , :foreign_key => 'created_by'
has_one :user , :foreign_key => 'modified_by'
end
class User
belongs_to :created_by , :class_name => 'Deal' , :foreign_key => 'created_by'
belongs_to :modified_by , :class_name => 'Deal' , :foreign_key => 'modified_by'
end
When I create the deal, looks like it is saving correctly. But in the show view when I try to get #deal.created_by.email I get an "undefined method email" error. Can some tell me how to get this working please?
Also since I have multiple models with these two columns, there can be many belongs_to in User model. Is there an elegant solution for this case?
First thing you have to add is the specification of accessible attributes.
In User you would have to add:
attr_accessible :email, :created_by, :modified_by
In Deal:
attr_accessible :created_by, :modified_by
But you should also change the direction of your relation. The foreign_key is always on the belongs_to side.
This is what worked for me:
class Deal < ActiveRecord::Base
belongs_to :created_by, :class_name => "User", :foreign_key => "created_by"
belongs_to :modified_by, :class_name => "User", :foreign_key =>"modified_by"
attr_accessible :created_by, :modified_by, :name
end
class User < ActiveRecord::Base
has_many :created_deals, :class_name => "Deal", :foreign_key => "created_by"
has_many :modified_deals, :class_name => "Deal", :foreign_key => "modified_by"
attr_accessible :created_deals, :modified_deals, :name
end
If you have more models, which look similiar you could probably use polymorphic associations: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
First of all, from my experience it is generally a bad idea to have associations using the foreign key as name. Especially when writing fixtures it seems rails will get confused between setting the actual value "created_by" or the model in the created_by association. In my models I generally use these associations for the cases you describe:
belongs_to :creator, :class_name => "User", :foreign_key => 'created_by'
belongs_to :modifier, :class_name => "User", :foreign_key => 'modified_by'
You can use association names like 'creating_user' instead if you prefer. If you really want created_by as association name you should have created_by_id or something similar as foreign key, just as long as its not equal to the association name.
Then I am a bit confused by your pasted code. Your choice "Deal has_one User" and "User belongs_to Deal" means that the users table will have the columns created_by and modified_by (foreign keys) containing Deal Ids, basically meaning that users get created by a single deal? However it seems like deals should get created by users and not the other way round. Your example of deal.created_by.email can not work at all with your associations, since deal would not have an association called "created_by", only "user", of which you have two associations with the same name in a single model which can not work at all in the first place.
Fixing your associations similar to what Patrick suggested:
class Deal < ActiveRecord::Base
belongs_to :creator, :class_name => "User", :foreign_key => "created_by"
belongs_to :modifier, :class_name => "User", :foreign_key =>"modified_by"
end
class User < ActiveRecord::Base
has_many :created_deals, :class_name => "Deal", :foreign_key => "created_by"
has_many :modified_deals, :class_name => "Deal", :foreign_key => "modified_by"
end

Rails Issue with associations using primary_key+foreign_key options

I want to access a legacy database schema from Rails. I have one table NAGIOS_OBJECTS with a primary key OBJECT_ID and one table NAGIOS_HOST_CHECKS that refers to NAGIOS_OBJECTS with a column HOST_OBJECT_ID. I thus defined the relations as follows:
class NagiosObject < ActiveRecord::Base
has_one :nagios_host_check, :foreign_key => :host_object_id, :primary_key => :object_id
end
class NagiosHostCheck < ActiveRecord::Base
belongs_to :nagios_object, :foreign_key => :host_object_id, :primary_key => :object_id
end
However, when calling a_nagios_object.nagios_host_check or a_nagios_host_check.nagios_object, I always get nil.
Any idea what is wrong with my code?
foreign_key and primary_key should be strings, not symbols
ex:
class NagiosObject < ActiveRecord::Base
has_one :nagios_host_check, :foreign_key => 'host_object_id', :primary_key => 'object_id'
end
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001317

Resources