has_one relation automatically set nil if more than one - ruby-on-rails

Give a two models, with a has_one association:
class ShopInfo
belongs_to :shop
end
class Shop
has_one :shop_info
end
s = Shop.create
ss1 = s.create_shop_info
In some other place of my code I do
ss2 = s.create_shop_info
After this, ss1.shop_id is set to nil, so ss1 is now an orphan record.
Is there any way to remove previous records instead of set them to nil?

By default, the has_one association executes a nullify. Adding the dependent: :destroy solved the problem.
class Shop
has_one :shop_info, dependent: :destroy
end
Just if someone wants more info, the ActiveRecord code for has_one replacement record is this:
https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/associations/has_one_association.rb#L24-L51
BUT if you add a dependent option in the association, executes the delete method as well:
https://github.com/rails/rails/blob/v4.2.6/activerecord/lib/active_record/associations/has_one_association.rb#L7-L22

Fran, if you look at the has_one documentation, I think you want to use the association= method:
association=(associate)
Assigns the associate object, extracts the
primary key, sets it as the foreign key, and saves the associate
object. To avoid database inconsistencies, permanently deletes an
existing associated object when assigning a new one, even if the new
one isn't saved to database.
Which means your code might look like...
ss2.shop_info = ShopInfo.new(...)

Related

Rails: How to Convert has_one to has_many association

Assuming
class Kid < ActiveRecord::Base
has_one :friend
end
class Friend< ActiveRecord::Base
belongs_to :kid
end
How can I change this to
class Kid < ActiveRecord::Base
has_many :friends
end
class Friend< ActiveRecord::Base
belongs_to :kid
end
Will appreciate your insight...
Collection
The bottom line is that if you change your association to a has_many :x relationship, it creates a collection of the associative data; rather than a single object as with the single association
The difference here has no bearing on its implementation, but a lot of implications for how you use the association throughout your application. I'll explain both
Fix
Firstly, you are correct in that you can just change your has_one :friend to has_many :friends. You need to be careful to understand why this works:
ActiveRecord associations work by associating something called foreign_keys within your datatables. These are column references to the "primary key" (ID) of your parent class, allowing Rails / ActiveRecord to associate them
As long as you maintain the foreign_keys for all your Friend objects, you'll get the system working no problem.
--
Data
To expand on this idea, you must remember that as you create a has_many association, Rails / ActiveRecord is going to be pulling many records each time you reference the association.
This means that if you call #kind.friends, you will no longer receive a single object back. You'll receive all the objects from the datatable - which means you'll have to call a .each loop to manipulate / display them:
#kid = Kid.find 1
#kid.friends.each do |friend|
friend.name
end
If after doing this changes you have problem calling the save method on the order.save telling you that it already exists, and it not allowing you to actually have many order records for one customer you might need to call orders.save(:validate=> false)
You have answered the question. Just change it in model as you've shown.

How to make associations work in built and unsaved objects?

Let's say I have something like:
#order = Order.new(status: :pending)
#item = #order.items.build(title: "Shirt")
When I try to call #item.order, I get an error not found, I guess it's because it's still unsaved to the DB, but how can make it point to the built object without saving any of the objects?
#item.order # Not Found - Tries to fetch from the DB
To make this work you have to tell the order association of your item model that it is the inverse of your items association or you order model and vice versa:
Like this:
class Item < ActiveRecord::Base
belongs_to :order, inverse_of: :items
end
class Order < ActiveRecord::Base
has_many :items, inverse_of: :order
end
That way your associations will be set up correctly even before saving to the database.
EDIT: If you don't want this you can always assign the association explicitly:
#item = #order.items.build(title: "Shirt")
#item.order = #order
or in one line:
#item = #order.items.build(title: "Shirt", order: #order)
I tested this in Rails 4.0.1 and it looks like using inverse_of is no longer needed as the associations are inferred properly. It was addressed in this commit: https://github.com/rails/rails/pull/9522
It is worth noting that there is still no solution for has_many through: relationships at this time.
That's because the #order haven't got an ID yet. So when calling #order.items.build the item has an nil in order_id field and thus cannot found the order you want.
I suggest you use the nested attributes feature to save some associated records together.

Is the has_may relationship on rails necessary or the belong_to is enough?

I have a model with a var reference to another one.
User -> Profile
When I have generated the Profile model I used the references
feature so it has generated the corresponding migration
....
t.references :user
....
My question is do I have to add a relationship on the User model too?
has_one :Profile
Yes, you need both code in two models and the migration you mentioned.
class User < AR
has_one :profile
end
class Profile < AR
belongs_to :user
end
has_one and belongs_to are just methods which adds some more methods to your model. This means, you can have belongs_to defined on one model and no has_one on the other. The only problem is that you would be able to call profile.user, but no user.profile.
It is absolutely up to you which methods you want to be defined and which you don't need. If you never ever want anyone to call profile.user, but want user.profile just call has_one :profile. In general those method shares nothing except that their using same foreign key column.
It is however worth mentioning, that this is usually advised to declare reverse association - it is not needed for things to work though.

Mongoid, how to keep relations in sync for common parent model?

I have three models:
Agency
has_many :owners
has_many :properties
Owner
belongs_to :agency
has_many :properties
Property
belongs_to :owner
belongs_to :agency
The agency.properties relation should refer to all properties that all owners have, but when I create a property inside an owner, the agency.properties relation is not created. I want this relation to be automatically fulfilled, as well as deleted when the owner or the property is deleted.
How can I achieve this behavior with mongoid?
You could also write something like:
Agency
has_many :owners
Owner
belongs_to :agency
has_many :properties
Property
belongs_to :owner
And then add an instance method in your Agency model:
def properties
owners.map(&:properties).flatten.uniq
end
However, this approach will query your database to retrieve the owners and then will query your DB again once per each owner to retrieve each owner's properties.
Hope this could help.
EDIT
There is another solution which implies just 2 queries:
def properties
Property.where({:owner_id.in => owner_ids})
end
PROS:
It uses only two queries.
It returns a Mongoid Criteria (the previous solution returned an array). Thus you can chain scopes and so on ( i.e. my_agency.properties.sold #if you have defined a sold scope)
CONS:
This code seems less readable.
Additionally it's less maintainable. If you change the foreign key in the Owner-Property relation you should update this method (Property.where({:foreign_key...}) or you change the way an owner has many properties. The first option is still valid as long as each owner properties can be found with the instance method some_owner.properties.
As this is a pretty specific use case, there is no way to 'automatically' do this using mongoid 'out of the box'. What I would suggest you is to have some before_save hooks, that would guarantee the state of your relations. You also, depending of your use case, Property could be a denormalized(embedded) object inside Owner and Agency.

has_one not updating attribute id?

So I have a DB relationship where a User has a PreferenceList. I have set up a preference_list_id in the database table, and I have set up a belongs_to :user relationship with PreferenceList and a has_one :preference_list relationship with User.
Here's the weird part. If I do user.preference_list = preference_list, I can access user.preference_list and it will give me the correct instance of preference_list. However, if I do user.preference_list_id (a valid, column, I've checked), it gives me nothing even after I have added preference_list to user.preference_list. I need the id to be updated for various database operations. What am I doing wrong?
The foreign key goes on the table for the class declaring the belongs_to association. So if you have
class PreferenceList < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :preference_list
end
table preferences_lists should have user_id columns. Rails will handle this columns properly. You can add preference_list_id column to users table as well. But this column has no special meaning, Rails will do nothing with it by default. Try to change belongs_to/has_one sides if you really need to operate preference_list_id. Or use user.preference_list.id

Resources