I have a table with entries, and each entries can have different account-types. I'm trying to define and return the account based on the value of cindof
Each account type has one table, account_site and account_page. So a regular belongs_to won't do.
So is there any way to return something like:
belongs_to :account, :class_name => "AccountSite", :foreign_key => "account_id" if cindof = 1
belongs_to :account, :class_name => "AccountPage", :foreign_key => "account_id" if cindof = 2
Have tried to do that in a method allso, but no luck. Really want to have just one accountand not different belongs_to names.
Anyone that can figure out what I want? Hard to explain in English.
Terw
You should be able to do what you want with a polymorphic association. This won't switch on cindof by default, but that may not be a problem.
class ObjectWithAccount < ActiveRecord::Base
belongs_to :account, :polymorphic => true
end
class AccountSite < ActiveRecord::Base
has_many :objects_with_accounts,
:as => :account,
:class_name => 'ObjectWithAccount'
end
class AccountPage < ActiveRecord::Base
has_many :objects_with_accounts,
:as => :account,
:class_name => 'ObjectWithAccount'
end
You will need both an account_id column and a account_type column. The type of the account object is then stored in the extra type column.
This will let you do:
obj.account = AccountPage.new
or
obj.account = AccountSite.new
I would look into Single Table Inheritance. Not 100% sure, but I think it would solve your problem http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html
If that isn't good, this isn't too hard to implement yourself.
def account
case self.cindof
when 1 then AccountSite.find self.account_id
when 2 then AccountPage.find self.account_id
end
end
How can I make a many-to-many relationship with the same model in rails?
For example, each post is connected to many posts.
There are several kinds of many-to-many relationships; you have to ask yourself the following questions:
Do I want to store additional information with the association? (Additional fields in the join table.)
Do the associations need to be implicitly bi-directional?
(If post A is connected to post B, then post B is also connected to post A.)
That leaves four different possibilities. I'll walk over these below.
For reference: the Rails documentation on the subject. There's a section called “Many-to-many”, and of course the documentation on the class methods themselves.
Simplest scenario, uni-directional, no additional fields
This is the most compact in code.
I'll start out with this basic schema for your posts:
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
For any many-to-many relationship, you need a join table. Here's the schema for that:
create_table "post_connections", :force => true, :id => false do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
end
By default, Rails will call this table a combination of the names of the two tables we're joining. But that would turn out as posts_posts in this situation, so I decided to take post_connections instead.
Very important here is :id => false, to omit the default id column. Rails wants that column everywhere except on join tables for has_and_belongs_to_many. It will complain loudly.
Finally, notice that the column names are non-standard as well (not post_id), to prevent conflict.
Now in your model, you simply need to tell Rails about these couple of non-standard things. It will look as follows:
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
:join_table => "post_connections",
:foreign_key => "post_a_id",
:association_foreign_key => "post_b_id")
end
And that should simply work! Here's an example irb session run through script/console:
>> a = Post.create :name => 'First post!'
=> #<Post id: 1, name: "First post!">
>> b = Post.create :name => 'Second post?'
=> #<Post id: 2, name: "Second post?">
>> c = Post.create :name => 'Definitely the third post.'
=> #<Post id: 3, name: "Definitely the third post.">
>> a.posts = [b, c]
=> [#<Post id: 2, name: "Second post?">, #<Post id: 3, name: "Definitely the third post.">]
>> b.posts
=> []
>> b.posts = [a]
=> [#<Post id: 1, name: "First post!">]
You'll find that assigning to the posts association will create records in the post_connections table as appropriate.
Some things to note:
You can see in the above irb session that the association is uni-directional, because after a.posts = [b, c], the output of b.posts does not include the first post.
Another thing you may have noticed is that there is no model PostConnection. You normally don't use models for a has_and_belongs_to_many association. For this reason, you won't be able to access any additional fields.
Uni-directional, with additional fields
Right, now... You've got a regular user who has today made a post on your site about how eels are delicious. This total stranger comes around to your site, signs up, and writes a scolding post on regular user's ineptitude. After all, eels are an endangered species!
So you'd like to make clear in your database that post B is a scolding rant on post A. To do that, you want to add a category field to the association.
What we need is no longer a has_and_belongs_to_many, but a combination of has_many, belongs_to, has_many ..., :through => ... and an extra model for the join table. This extra model is what gives us the power to add additional information to the association itself.
Here's another schema, very similar to the above:
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
create_table "post_connections", :force => true do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
t.string "category"
end
Notice how, in this situation, post_connections does have an id column. (There's no :id => false parameter.) This is required, because there'll be a regular ActiveRecord model for accessing the table.
I'll start with the PostConnection model, because it's dead simple:
class PostConnection < ActiveRecord::Base
belongs_to :post_a, :class_name => :Post
belongs_to :post_b, :class_name => :Post
end
The only thing going on here is :class_name, which is necessary, because Rails cannot infer from post_a or post_b that we're dealing with a Post here. We have to tell it explicitly.
Now the Post model:
class Post < ActiveRecord::Base
has_many :post_connections, :foreign_key => :post_a_id
has_many :posts, :through => :post_connections, :source => :post_b
end
With the first has_many association, we tell the model to join post_connections on posts.id = post_connections.post_a_id.
With the second association, we are telling Rails that we can reach the other posts, the ones connected to this one, through our first association post_connections, followed by the post_b association of PostConnection.
There's just one more thing missing, and that is that we need to tell Rails that a PostConnection is dependent on the posts it belongs to. If one or both of post_a_id and post_b_id were NULL, then that connection wouldn't tell us much, would it? Here's how we do that in our Post model:
class Post < ActiveRecord::Base
has_many(:post_connections, :foreign_key => :post_a_id, :dependent => :destroy)
has_many(:reverse_post_connections, :class_name => :PostConnection,
:foreign_key => :post_b_id, :dependent => :destroy)
has_many :posts, :through => :post_connections, :source => :post_b
end
Besides the slight change in syntax, two real things are different here:
The has_many :post_connections has an extra :dependent parameter. With the value :destroy, we tell Rails that, once this post disappears, it can go ahead and destroy these objects. An alternative value you can use here is :delete_all, which is faster, but will not call any destroy hooks if you are using those.
We've added a has_many association for the reverse connections as well, the ones that have linked us through post_b_id. This way, Rails can neatly destroy those as well. Note that we have to specify :class_name here, because the model's class name can no longer be inferred from :reverse_post_connections.
With this in place, I bring you another irb session through script/console:
>> a = Post.create :name => 'Eels are delicious!'
=> #<Post id: 16, name: "Eels are delicious!">
>> b = Post.create :name => 'You insensitive cloth!'
=> #<Post id: 17, name: "You insensitive cloth!">
>> b.posts = [a]
=> [#<Post id: 16, name: "Eels are delicious!">]
>> b.post_connections
=> [#<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>]
>> connection = b.post_connections[0]
=> #<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>
>> connection.category = "scolding"
=> "scolding"
>> connection.save!
=> true
Instead of creating the association and then setting the category separately, you can also just create a PostConnection and be done with it:
>> b.posts = []
=> []
>> PostConnection.create(
?> :post_a => b, :post_b => a,
?> :category => "scolding"
>> )
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> b.posts(true) # 'true' means force a reload
=> [#<Post id: 16, name: "Eels are delicious!">]
And we can also manipulate the post_connections and reverse_post_connections associations; it will neatly reflect in the posts association:
>> a.reverse_post_connections
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> a.reverse_post_connections = []
=> []
>> b.posts(true) # 'true' means force a reload
=> []
Bi-directional looped associations
In normal has_and_belongs_to_many associations, the association is defined in both models involved. And the association is bi-directional.
But there is just one Post model in this case. And the association is only specified once. That's exactly why in this specific case, associations are uni-directional.
The same is true for the alternative method with has_many and a model for the join table.
This is best seen when simply accessing the associations from irb, and looking at the SQL that Rails generates in the log file. You'll find something like the following:
SELECT * FROM "posts"
INNER JOIN "post_connections" ON "posts".id = "post_connections".post_b_id
WHERE ("post_connections".post_a_id = 1 )
To make the association bi-directional, we'd have to find a way to make Rails OR the above conditions with post_a_id and post_b_id reversed, so it will look in both directions.
Unfortunately, the only way to do this that I know of is rather hacky. You'll have to manually specify your SQL using options to has_and_belongs_to_many such as :finder_sql, :delete_sql, etc. It's not pretty. (I'm open to suggestions here too. Anyone?)
To answer the question posed by Shteef:
Bi-directional looped associations
The follower-followee relationship among Users is a good example of a Bi-directional looped association. A User can have many:
followers in its capacity as followee
followees in its capacity as follower.
Here's how the code for user.rb might look:
class User < ActiveRecord::Base
# follower_follows "names" the Follow join table for accessing through the follower association
has_many :follower_follows, foreign_key: :followee_id, class_name: "Follow"
# source: :follower matches with the belong_to :follower identification in the Follow model
has_many :followers, through: :follower_follows, source: :follower
# followee_follows "names" the Follow join table for accessing through the followee association
has_many :followee_follows, foreign_key: :follower_id, class_name: "Follow"
# source: :followee matches with the belong_to :followee identification in the Follow model
has_many :followees, through: :followee_follows, source: :followee
end
Here's how the code for follow.rb:
class Follow < ActiveRecord::Base
belongs_to :follower, foreign_key: "follower_id", class_name: "User"
belongs_to :followee, foreign_key: "followee_id", class_name: "User"
end
The most important things to note are probably the terms :follower_follows and :followee_follows in user.rb. To use a run of the mill (non-looped) association as an example, a Team may have many :players through :contracts. This is no different for a Player, who may have many :teams through :contracts as well (over the course of such Player's career). But in this case, where only one named model exists (i.e. a User), naming the through: relationship identically (e.g. through: :follow, or, like was done above in the posts example, through: :post_connections) would result in a naming collision for different use cases of (or access points into) the join table. :follower_follows and :followee_follows were created to avoid such a naming collision. Now, a User can have many :followers through :follower_follows and many :followees through :followee_follows.
To determine a User’s :followees (upon an #user.followees call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the follower (i.e. foreign_key: :follower_id) through: such User’s :followee_follows. To determine a User’s :followers (upon an #user.followers call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the followee (i.e. foreign_key: :followee_id) through: such User’s :follower_follows.
If anyone came here to try to find out how to create friend relationships in Rails, then I would refer them to what I finally decided to use, which is to copy what 'Community Engine' did.
You can refer to:
https://github.com/bborn/communityengine/blob/master/app/models/friendship.rb
and
https://github.com/bborn/communityengine/blob/master/app/models/user.rb
for more information.
TL;DR
# user.rb
has_many :friendships, :foreign_key => "user_id", :dependent => :destroy
has_many :occurances_as_friend, :class_name => "Friendship", :foreign_key => "friend_id", :dependent => :destroy
..
# friendship.rb
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
Inspired by #Stéphan Kochen,
this could work for bi-directional associations
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
join_table: 'post_connections',
foreign_key: 'post_a_id',
association_foreign_key: 'post_b_id')
has_and_belongs_to_many(:reversed_posts,
class_namy: Post,
join_table: 'post_connections',
foreign_key: 'post_b_id',
association_foreign_key: 'post_a_id')
end
then post.posts && post.reversed_posts should both works, at least worked for me.
For bi-directional belongs_to_and_has_many, refer to the great answer already posted, and then create another association with a different name, the foreign keys reversed and ensure that you have class_name set to point back to the correct model. Cheers.
If anyone had issues getting the excellent answer to work, such as:
(Object doesn't support #inspect)
=>
or
NoMethodError: undefined method `split' for :Mission:Symbol
Then the solution is to replace :PostConnection with "PostConnection", substituting your classname of course.
I'm trying to implement a social networking style friendship model and I didnt have much much luck trying to figure out the plugins available out there. I think I'll learn Rails better if I do it myself. So here's what I have :
class User < ActiveRecord::Base
has_many :invitee_friendships ,
:foreign_key => :friend_id,
:class_name => 'Friendship'
has_many :inviter_friends,
:through => :invitee_friendships
has_many :inviter_friendships ,
:foreign_key => :user_id,
:class_name => 'Friendship'
has_many :invited_friends,
:through => :inviter_friendships
end
class Friendship < ActiveRecord::Base
belongs_to :user
//I think something needs to come here, i dont know what
end
In irb when I try this:
friend1 = Friend.create(:name => 'Jack')
friend2 = Friend.create(:name => 'John')
bff = Friendship.create(:user_id =>1, :friend_id => 2)
f1.invited_friends
I get an error:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError:
Could not find the source
association(s) :invited_friend or
:invited_friends in model Friendship.
Try 'has_many :invited_friends,
:through => :invited_friendships,
:source => <name>'. Is it one of
:user?
Expanation of friendship system:
A user can invite other users to become friends.
Users who you invited to become friends are represented by invited_friends.
Users who invited you to become friends are represented by inviter_friends.
Your total friend list is represented by invited_friends + inviter_friends.
Schema
table Friendship
t.integer :user_id
t.integer :friend_id
t.boolean :invite_accepted
t.timestamps
table User
t.string :name
t.string :description
I'm surprised no one has pointed to the recent Ryan Bates's screencast on the topic :)
Hope this helps!.
Excerpt from Ryan '... requires a self-referential association on the User model to define friends/followers'
I'm currently writing some intranet web application where people could submit to admins requests for adding different resources. The example requests would be:
installing programs, in this case user will select which program he wants installed
increasing quota, in this case user will just enter the amount of disk space he needs or maybe he will select the predefined quantities - 1GB, 10GB etc...
create new email alias, in this case user will just type the alias.
...
I was thinking about having just one model UserRequests with the reference to the sender and
two optional attributes one would be reference_id that would refefrence to other tables (for
example the Program that he wants installed) and another would be used for free type fields
like email alias or quota.
So my problem is that based on the type of the request the model should contain either:
reference to other table
integer data
string data
Based on the type of the request the given action should be taken - probably email alias
could be added from rails but the application on users computer will be installed by hand.
Does anyone had similar problem? Do you think using polymorphism for this kind of stuff is a good idea? Do you have any suggestions on how to organize data in the tables?
Single Table Inheritance! This way you can have each type of request have custom validations, while still having every request live in the same table.
class CreateUserRequests < ActiveRecord::Migration
def self.up
create_table :user_requests do |t|
t.string :string_data, :type
t.integer :user_id, :integer_data
t.timestamps
end
end
def self.down
drop_table :user_requests
end
end
class UserRequest < ActiveRecord::Base
belongs_to :user
end
class EmailAliasRequest < UserRequest
validates_presence_of :string_data
validates_format_of :string_data, :with => EMAIL_REGEX
end
class ProgramInstallRequest < UserRequest
belongs_to :program, :class_name => "Program", :foreign_key => "integer_data"
validates_presence_of :integer_data
end
class QuotaIncreaseRequest < UserRequest
validates_presence_of :string_data
validates_inclusion_of :string_data, :in => %w( 1GB 5GB 10GB 15GB )
end
And of course, alias your string_data and integer_data to email or whatnot to make your other code have a little more meaning. Let the model be the little black box that hides it all away.
I would use polymorphic associations, which let a model belong to more than one other model using a single association. Something like this:
class AdminRequest < ActiveRecord::Base
belongs_to :user
belongs_to :requestable, :polymorphic => true
end
class EmailAlias < ActiveRecord::Base
has_many :admin_requests, :as => :requestable
end
class ProgramInstall < ActiveRecord::Base
has_many :admin_requests, :as => :requestable
end
class QuotaIncrease < ActiveRecord::Base
has_many :admin_requests, :as => :requestable
end
As ever, Ryan Bates has an excellent Railscast on the subject.