Many-to-many relationship with the same model in rails? - ruby-on-rails

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.

Related

Rails 4 converting a polymorphic association to has_many through

A Manager has many contacts via polymorphic association
class Manager
has_many :contacts, as: :contactable
end
class Contact
belongs_to :contactable, polymorphic: true
end
The relation works fine but now a contact can be associated to many managers.
So, added a new model Contactable, a joins table 'contactables' and moved contactable_id and contactable_type fields from contacts table to contactables table.
class Contactable
belongs_to :contact
belongs_to :contactable, polymorphic: true
end
Now confused about the Manager and Contact relation that how it would be defined in models correctly to make it working. Tried the following but it doesn't work:
class Manager
has_many :contacts, through: :contactables, source: :contactable, source_type: "Contact"
end
So I checked this interesting topic and will tell what I know.
When you create objects as usual in has_many :through:
class Contact
has_many :contactables
has_many :managers, :through => :contactables
end
class Manager
has_many :contactables
has_many :contacts, :through => :contactables
end
class Client
has_many :contactables
has_many :contacts, :through => :contactables
end
class Contactable
belongs_to :contact
belongs_to :manager
belongs_to :client
end
You get to use foreign keys fro each referenced object. Polymorphic looks like a great solution. So:
class Contactable
belongs_to :contact
belongs_to :polymorphic_model, polymorphic: true
end
class Contact
has_many :contactables
has_many :managers, :through => :contactables, :source => :polymorphic_model, :source_type => 'Manager'
end
class Manager
has_many :contactables, :as => :polymorphic_model
has_many :contacts, :through => :contactables
end
Setting the :as option indicates that this is a polymorphic
association
:source => :polymorphic_model is used to tell Rails to get the related object from the subclass. :source means the same as :class_name. Without this option Rails would try to get associated Manager from the Contactables table, while it should be reached via virtual Polymorphic_model.
By adding belongs_to :polymorphic_model to Contactable you enable Contact (witch already sits there, because of belongs_to :contact) to be associated with a Manager or Client, because thats what Polymorphic association does - references two or more parent tables. And because Contact have_many Contactables, the same Contact object can be associated with many managers or clients. So after you understand it, it looks really simple - Joined model belongs to Contact and Joined model also holds references to Manager and Client through Polymorphic association. So in order for Contact to have many managers, you create another Contactable object that belongs to the same Contact, but different Manager. Doesn't look super efficient, but personally me, not knowing a better way..
Here is a tested proof:
Manager.create!(name: "Bravo")
=> #<Manager id: 1, created_at: "2017-04-12 12:17:41", updated_at: "2017-04-12 12:17:41", name: "Bravo">
Manager.create!(name: "Johnny")
=> #<Manager id: 2, created_at: "2017-04-12 12:18:24", updated_at: "2017-04-12 12:18:24", name: "Johnny">
Contact.create!(number:"123")
=> #<Contact id: 1, created_at: "2017-04-12 12:18:59", updated_at: "2017-04-12 12:18:59", number: 123>
c = Contactable.new
c.contact = Contact.first
c.unit = Manager.first
c
=> #<Contactable id: nil, unit_type: "Manager", created_at: nil, updated_at: nil, unit_id: 1, contact_id: 1>
Now to set another Manager to the same contact, we create a new Contactable:
cc = Contactable.new
cc.contact = Contact.first
cc.unit = Manager.last
cc
=> #<Contactable id: nil, unit_type: "Manager", created_at: nil, updated_at: nil, unit_id: 4, contact_id: 1>
And to get all associated:
Contact.first.managers
Contactable's database:
contact_id
unit_id
unit_type
And one interesting quote by #Bill Karwin:
The Polymorphic Associations design breaks rules of relational
database design. I don't recommend using it.
But he wrote this long time ago. Probably irrelevant now.
Why can you not have a foreign key in a polymorphic association?

Rails 4: Has_one polymorphic association not working

I have two models Purchase and Address. I'm trying to make Address polymorphic so I can reuse it in my Purchase model for has_one :billing_address and has_one :shipping_address. Here's my schema:
create_table "addresses", force: true do |t|
t.string "first_name"
t.string "last_name"
t.string "street_address"
t.string "street_address2"
t.string "zip_code"
t.string "phone_number"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "state_id"
t.string "city"
t.string "addressable_type" #<--
t.integer "addressable_id" #<--
end
address model:
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
...
end
purchase model:
class Purchase < ActiveRecord::Base
has_one :shipping_address, as: :addressable
has_one :billing_address, as: :addressable
...
end
Everything looks fine to me, but my Rspec tests fail:
Failures:
1) Purchase should have one shipping_address
Failure/Error: it { should have_one(:shipping_address) }
Expected Purchase to have a has_one association called shipping_address (ShippingAddress does not exist)
# ./spec/models/purchase_spec.rb:22:in `block (2 levels) in <top (required)>'
2) Purchase should have one billing_address
Failure/Error: it { should have_one(:billing_address) }
Expected Purchase to have a has_one association called billing_address (BillingAddress does not exist)
# ./spec/models/purchase_spec.rb:23:in `block (2 levels) in <top (required)>'
It doesn't seem to be detecting that the association is polymorphic. Even in my console:
irb(main):001:0> p = Purchase.new
=> #<Purchase id: nil, user_id: nil, order_date: nil, total_cents: 0, total_currency: "USD", shipping_cents: 0, shipping_currency: "USD", tax_cents: 0, tax_currency: "USD", subtotal_cents: 0, subtotal_currency: "USD", created_at: nil, updated_at: nil, status: nil>
irb(main):002:0> p.shipping_address
=> nil
irb(main):003:0> p.build_shipping_address
NameError: uninitialized constant Purchase::ShippingAddress
What am I doing wrong??
You need to specify the :class_name option for the has_one association, as the class name can't be inferred from the association name i.e., :shipping_address and :billing_address in your case doesn't give an idea that they refer to class Address.
Update the Purchase model as below:
class Purchase < ActiveRecord::Base
has_one :shipping_address, class_name: "Address", as: :addressable
has_one :billing_address, class_name: "Address", as: :addressable
## ...
end
I think you've misunderstood what polymorphic associations are for. They allow it to belong to more than one model, Address here is always going to belong to Purchase.
What you've done allows an Address to belong to say, Basket or Purchase. The addressable_type is always going to be Purchase. It won't be ShippingAddress or BillingAddress which I think you think it will be.
p.build_shipping_address doesn't work because there isn't a shipping address model.
Add class_name: 'Address' and it will let you do it. However it still won't work the way you expect.
I think what you actually want is single table inheritance. Just having a type column on address
class Purchase < ActiveRecord::Base
has_one :shipping_address
has_one :billing_address
end
class Address < ActiveRecord::Base
belongs_to :purchase
...
end
class ShippingAddress < Address
end
class BillingAddress < Address
end
This should be fine because shipping and billing address will have the same data, if you've got lots of columns that are only in one or the other it's not the way to go.
Another implementation would be to have shipping_address_id and billing_address_id on the Purchase model.
class Purchase < ActiveRecord::Base
belongs_to :shipping_address, class_name: 'Address'
belongs_to :billing_address, class_name: 'Address'
end
The belongs_to :shipping_address will tell rails to look for shipping_address_id with the class name telling it to look in the addresses table.
It is somewhat of a convention to name the polymorphic relation something that ends in "able". This makes sense if it conveys some sort of action. For example and image is "viewable", an account is "payable", etc. However, sometimes it is hard to come up with a term that ends in "able" and when forced to find such a term, it can even cause a bit of confusion.
Polymorphic associations often show ownership. For this case in particular, I would would define the Address model as follows:
class Address < ActiveRecord::Base
belongs_to :address_owner, :polymorphic => true
...
end
Using :address_owner for the relation name should help clear up any confusion as it makes it clear that an address belongs to something or someone. It could belong to a user, a person, a customer, an order, or a business, etc.
I would argue against single table inheritance as shipping and billing addresses are still at the end of the day, just addresses, i.e, there is no morphing going on. In contrast, a person, an order, or a business are cleary very different in nature and therefore make a good case for polymorphism.
In the case of an order, we still want the columns address_owner_type and address_owner_id to reflect that an order belongs to a customer. So the question remains: How do we show that an order has a billing address and a shipping address?
The solution that I would go with is to add foreign keys to the orders table for the shipping address and billing address. The Order class would look something like the following:
class Order < ActiveRecord::Base
belongs_to :customer
has_many :addresses, :as => :address_owner
belongs_to :shipping_address, :class_name => 'Address'
belongs_to :billing_address, :class_name => 'Address'
...
end
Note: I am using Order for the class name in favor of Purchase, as an order reflects both the business and customer sides of a transaction, whereas, a purchase is something that a customer does.
If you want, you can then define the opposite end of the relation for Address:
class Address < ActiveRecord::Base
belongs_to :address_owner, :polymorphic => true
has_one :shipping_address, :class_name => 'Order', :foreign_key => 'shipping_address_id'
has_one :billing_address, :class_name => 'Order', :foreign_key => 'billing_address_id'
...
end
The one other bit of code to clear up yet, is how to set the shipping and billing addresses? For this, I would override the respective setters on the Order model. The Order model would then look like the following:
class Order < ActiveRecord::Base
belongs_to :customer
has_many :addresses, :as => :address_owner
belongs_to :shipping_address, :class_name => 'Address'
belongs_to :billing_address, :class_name => 'Address'
...
def shipping_address=(shipping_address)
if self.shipping_address
self.shipping_address.update_attributes(shipping_address)
else
new_address = Address.create(shipping_address.merge(:address_owner => self))
self.shipping_address_id = new_address.id # Note of Caution: Replacing this line with "self.shipping_address = new_address" would re-trigger this setter with the newly created Address, which is something we definitely don't want to do
end
end
def billing_address=(billing_address)
if self.billing_address
self.billing_address.update_attributes(billing_address)
else
new_address = Address.create(billing_address.merge(:address_owner => self))
self.billing_address_id = new_address.id
end
end
end
This solution solves the problem by defining two relations for an address. The has_one and belongs_to relation allows us to track shipping and billing addresses, whereas the polymorphic relation shows that the shipping and billing addresses belong to an order. This solution gives us the best of both worlds.

Rails has_many with source and conditions doesn't create condition attributes

I'm trying to setup a has_many with conditions which works fine for the reading part but not for new entries. I've tested it some weeks ago in a sandbox and it worked but I can't get it work again so maybe I'm just blind or it is just a wrong design :-)
class Task
has_many :task_users
has_many :assignees, :through => :task_users, :source => :user, :conditions => {"task_users.is_assignee" => true}
has_many :participants, :through => :task_users, :source => :user
end
class TaskUser < ActiveRecord::Base
belongs_to :user
belongs_to :task
end
class User
has_many :tasks
end
After adding a new assignee to a task like this
Task.first.assignees << User.first
the following SQL is executed
SQL (0.3ms) INSERT INTO `task_users` (`created_at`, `is_assignee`, `task_id`, `updated_at`, `user_id`) VALUES ('2012-11-18 15:52:24', NULL, 2, '2012-11-18 15:52:24', 3)
I thought rails will use my conditions to set these values when I'm add ing new ones. Reading works great but I have no idea why adding new values doesn't work with conditions.
I expect this INSERT
SQL (0.3ms) INSERT INTO `task_users` (`created_at`, `is_assignee`, `task_id`, `updated_at`, `user_id`) VALUES ('2012-11-18 15:52:24', 1, 2, '2012-11-18 15:52:24', 3)
I'm not entirely sure whether you can specify :conditions hash on the join table in a has_many :through association. Someone else correct me if I'm wrong, but the condition has to be directly on the source association, :user in your case.
If this is the case, to work around this you can specify an auxiliary association:
has_many :task_users
has_many :assignee_task_users, :class_name => 'TaskUser', :conditions => {"is_assignee" => true}
has_many :assignees, :through => :assignee_task_users, :source => :user
just going to highlight the documentation:
Specify the conditions that the associated objects must meet in order to be
included as a WHERE SQL fragment, such as price > 5 AND name LIKE 'B%'.
Record creations from the association are scoped if a hash is used.
has_many :posts, :conditions => {:published => true} will create published
posts with #blog.posts.create or #blog.posts.build.
even though you used an hash already, the first parameter is a string, which is unneded to be (the association knows already the table name). rewrite it as :conditions => {:is_assignee => true} and it should work.
Also, the way you are creating users should be rewritten in order for this to work of course. Instead of:
Task.first.assignees << User.first
use:
Task.first.assignees.create
and that should do the trick.

loaded? always returns true for belongs_to associations?

I have some geography related classed defined as follows:
class Location < ActiveRecord::Base
end
class State < Location
has_many :geographies
has_many :cities
has_many :counties
end
class County < Location
belongs_to :state
has_many :geographies
has_many :cities, :through => :geographies, :uniq => true
end
class City < Location
belongs_to :state
has_many :geographies
has_many :counties, :through => :geographies, :uniq => true
end
class Geography < ActiveRecord::Base
belongs_to :state
belongs_to :city
belongs_to :county
end
The following console output demonstrates the problem that I'm having.
>> County.first.cities.loaded?
=> false
>> County.first.state.loaded?
=> true
Looking in the logs I see that when I call state.loaded? it runs a SQL statement to load the state, i.e., the state was not loaded until I "touched" it. However, when I call cities.loaded? no SQL is executed and false is returned. This behavior seems inconsistent to me. I searched around a bit on the interwebs and couldn't find anything regarding this so I'm guessing it's my mistake, though, I don't see how.
Any help is much appreciated.
Thanks in advance.
I think it comes down to the type of relationship being used and the interaction with lazy loading.
When you call County.first.state, Rails will load the state object that belongs to the County - there is only one, and therefore a call to .state is actually a call to a 'concrete' object in the database that can be loaded.
However, when you call County.first.cities, you are actually calling the relationship collection that belongs to the county object. As you don't actually call on a specific object or set of conditions, Rails is smart enough not to load the collection.
If you said something like:
County.first.cities.all
County.first.cities.each
County.first.cities.first
Rails would then kick-off the SQL statement and load the data.
I found that you can use the Object method instance_variable_get to check to see if a belongs_to association is loaded without triggering the association to be loaded during your check. So...
>> c = County.first
=> #<County id: 1, ...>
>> c.instance_variable_get("#state")
=> nil
>> c.state
=> #<State id: 1, ...>
>> c.instance_variable_get("#state")
=> #<State id: 1, ...>
>> c = County.first(:include => :state)
=> #<County id: 1, ...>
>> c.instance_variable_get("#state")
=> #<State id: 1, ...>

How do I do reflexive self-join relationships in ActiveRecord?

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'

Resources