Mongoid, how to keep relations in sync for common parent model? - ruby-on-rails

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.

Related

Rails Associations - Do I need has many and also belongs to together

If I have a class Category and a class Post, post belongs to a category then I make a t.belongs_to in Post migration, but do I have to necessarily declare a t.has_many in Category? If not, if I have to use just one, which is the best to choose?
Both belongs_to and has_many will just create an association method.
For example, use this:
class User
has_many :cars
end
if you want to be able to access cars from current user. Such as: current_user.cars.
Or use this:
class Car
belongs_to :user
end
if you want to be able to access user from the instance of car. For example: Car.lost_and_found.user.
In most cases, you will usually need both ways, so then use both.
No you don't need to. t.has_many is not available on migrations and also more importantly, doesn't do anything for the table. However, belongs_to actually translates to a foreign key in the table linking the relationship.
For all methods that exist for a create_table e.g. creating references which also an alias for belongs_to refer to this documentation:
http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/create_table#227-All-methods

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.

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.

Can has_one association be used when the model has one or zero instances of another model?

RailsGuides says:
http://guides.rubyonrails.org/association_basics.html
A has_many "association indicates that each instance of the model has zero or more instances of another model."
"A has_one association also sets up a one-to-one connection with another model, but with somewhat different semantics (and consequences). This association indicates that each instance of a model contains or possesses one instance of another model."
Does that mean if I want to set up an association that each instance of the model has zero or one instance of another model, the best way is to use has_many and not has_one? What will be the problems I'll encounter if I use has_one?
Thanks.
has_one is correct - the relationship that's set up is not mandatory unless you add your own validations to it.
To make it a bit clearer -
class Post < ActiveRecord::Base
has_one :author
end
class Author < ActiveRecord::Base
belongs_to :post
end
With no validations, a given post can have an author (but not more than one) - however an author is not necessary.
Unless you define specific validations, has_one just prevents you from having more than one object associated to your model.
Zero is ok.

Is it possible to create an ambiguous foreign key that gets defined on its row's creation?

I have two database tables, houses and apartments. Each of these has its own view page that I would like users to comment on. Naturally, I also have a posts table. When a post is created it should have a foreign key id referencing the appropriate table (either houses or apartments). Is there an efficient method to create some type of ambiguous foreign key for the posts table and define it on creation? As of now, the best solution to my problem I've thought of is to create two foreign key ids (one for houses and one for apartments) and just have a one column overhead for each post. Thanks for the help!
Single Table Inheritance allows an object to be defined as separate classes while using only a single database table. This is appropriate for objects that are structurally and functionally similar except for slight behavioral differences.
In your case, you have two subclasses, House and Apartment, which are very similar and can be abstracted to a single superclass, Dwelling. This superclass will contain common methods accessible to both houses and apartments. Each subclass will inherit Dwelling and expand it with their own subclass-specific methods. You will instantiate a subclass like normal, but internally Rails will be working with a typecast instance of the superclass. As such, you should never instantiate the superclass directly.
It is worth noting that STI combines all attributes into one table. Attributes for one subclass will be present in another, and are nil if unused. Subclasses with tons of extraneous attributes will result in bloated tables filled with nil values. As a general rule, polymorphic associations should be considered when your subclasses contain more differences than similarities.
Here's how we do. All of this goes in app/models/dwelling.rb:
class Dwelling < ActiveRecord::Base
has_many :posts
# ...common methods...
end
class House < Dwelling
has_many :posts, :foreign_key => "dwelling_id"
# ...house methods...
end
class Apartment < Dwelling
has_many :posts, :foreign_key => "dwelling_id"
# ...apartment methods...
end
In order for this to work, your dwelling table must contain a string column called "type". Rails will see this and automatically know that STI is being used. Note: Both house and apartment tables will no longer be used by the application. Creating a House object will save a new dwelling record to the database, with it's own unique ID and its type will be "house".
When assigning a new Post to a subclass, use the subclass object's ID. Subclass ID and superclass ID both refer to the same record. Note the has_many methods in each of the subclasses. Get a subclass' posts by calling #house.posts.
To infer whether a posting was posted to a house or apartment, you may use #post.dwelling.type.
Edit:
Rails 3's autoloading behavior expects each class to be in it's own file. Therefore, subclasses must be in their own files respectively:
app/
models/
apartment.rb
dwelling.rb
house.rb
Polymorphic Association should do the trick. You want posts to either reference a house and an apartment. You will have to add the polymorphic association via your models and then add the appropriate fields for your tables.
If you do decide to go the polymorphic route this how your models would look.
Posts
#Attributes
housing_type :string #Either will `House` or `Apartment`
housing_id :integer #This will reference the id of House or Apartment, depending on what's in your housing_type field
#Associations
belongs_to :housing, :polymorphic => true
Houses
#Attributes
post_id :integer
#Associations
has_many :posts, :as => :housing
Apartments
#Attributes
post_id :integer
#Associations
has_many :posts, :as => :housing
A basic guide on understanding and creating a polymorphic association.
And of course a RailsCast on it.

Resources