Rails determine if association is has_one or has_many - ruby-on-rails

Wondering if there is an easy way to determine dynamically if a model's association is a "has_one" or "has_many" relationship (i.e. is this an association to one object or many).
I'm using MongoMapper, so I am able to check if a class klass has an associated model assoc with a one or many relationship via
klass.associations[:assoc].is_a? MongoMapper::Plugins::Associations::OneAssociation
klass.associations[:assoc].is_a? MongoMapper::Plugins::Associations::ManyAssociation
but this seems rather clunky, and isn't generic (i.e. won't work for ActiveRecord associations as well). I'd also like to avoid loading any objects, so I'm pretty sure that instance.assoc.is_a? Array is out too.
Any ideas?

UPDATE: So, I happened upon the Reflections Class methods http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html
You can get all the has_many, belongs_to, etc. with reflect_on_all_associations method. It's all in there. Or you can put in an association into reflect_on_association and it will tell you if it is a has_many, has_one, etc. Specifically:
Model.reflect_on_association(:assoc).macro
This should be sufficient for all cases. It doesn't actually solve the problem I'm currently working on, but it should solve this.

Related

Managing polymorphic data in Rails

I have an application where a User can create many Links, and each Link can store different type of data, depending on what type of Link it is. For example, a TelephoneLinkData stores a telephone number, an EmailLinkData stores an email address, a subject and a body. Each Link also has some fields in common, such as a reference to the user and a name.
I've tried to map this into ActiveRecord as cleanly as I can. Currently, I have a polymorphic relationship from Link to its data-type:
class Link < ApplicationRecord
belongs_to :user
belongs_to :link_data, polymorphic: true
...
class EmailLinkData < ApplicationRecord
has_one :link, as: :link_data
accepts_nested_attributes_for :links
...
Technically, I think this would be described as a reverse polymorphic relationship as instead of a class having possibly different parent classes, what I'm trying to model is a class having multiple possible different child classes. This works fine, and I'm able to create Links through the various *LinkData controllers, but what I'd really want to do is have the Link act as the primary source of interaction for the user, so that the user manages their links through the /links path. For example, I would like the API to allow a User to create a link by posting to /links with the data for the LinkData nested in the link_data field
I've looked around for other ways to model this relationship, and the most common other suggestion seems to be Single-Table Inheritance, but the majority of my columns will differ between LinkData classes, so that feels like the wrong abstraction.
Is there a more idiomatic way to model this data structure?
As is always the case, the best choice depends on the business or application needs, so it's difficult to provide a recommendation without knowing more about what you're trying to do.
It sounds like you prefer the MTI approach, essentially using actual foreign keys and an XOR constraint to the Link table instead of a type column. That's a totally reasonable (although not as common) alternative to a polymorphic association.
However, I think there was a bit of a misunderstanding in your question.
Technically, I think this would be described as a reverse polymorphic relationship as instead of a class having possibly different parent classes...
A polymorphic association in Ruby/Rails doesn't have anything to do with class inheritance (e.g. parents and children). You might be thinking of Single table inheritance. A polymorphic association allows one class (e.g. a Link) to be associated a record in any other table (e.g. the various classes of LinkData) via two fields, a association_id and association_type. These associated classes need not be related to each other. For example, a common use case might be the acts_as_commentable gem, that allows you to add a comment to any other object, and the comment would have a polymorphic association with the other classes.
In the second part of your question you mention that you'd like the User to interact with Link's via a single controller.
I would like the API to allow a User to create a link by posting to /links with the data for the LinkData nested in the link_data field
There's nothing stopping you from implementing this using the initially proposed data model. ActiveRecord may not handle this completely for you out of the box, but you can imagine implementing a link_data= method on the Link class that would create the appropriate associated object.
I'd say the pros/cons of using a polymorphic association would be...
Pros:
easy to setup and use
easy to make required (validate presence of / not null)
easy to associate with a new class
Cons:
no referential / database integrity
have to migrate data if you change a class name
And using the MTI approach is basically the opposite. A bit harder to setup and use, harder to add a new association/table, harder to ensure exactly one association exists... but the long term data quality benefits are significant.
I was able to get things to work the way I wanted to using multiple table inheritance, based largely on this chapter: https://danchak99.wordpress.com/enterprise-rails/chapter-10-multiple-table-inheritance/

Why does Rails use the base class name, and not the subclass name, with polymorphic associations and STI?

Say I have Single Table Inheritance with BuyerInvoice inheriting from Invoice.
If I assign such an invoice to a polymorphic association, Rails will store e.g. record_type: "Invoice" rather than record_type: "BuyerInvoice". It stores record.class.base_class.name.
What are some reasons they may have done this? I'm implementing something vaguely similar and would like to understand why Rails might have made that decision.
Best I can think of is that it makes it a bit easier to rename subclasses without affecting associations, though doing it the other way would make it easier to rename abstract superclasses…
STI is an implementation detail of a model, it should not leak into its relations.
Other than renaming subclasses:
STI can be implemented with custom class resolution (with type column not being a class name at all), even have some weird cases where class changes dynamically based on attributes etc.
STI can be added to a model after it already has relations
STI can be removed at all

In Ruby on Rails, does a child class inherit the parent class' associations?

I have an abstract class called Work and another class I called Fanfic. In the Fanfic model, I had that it belongs_to :user and has_many :characters along with other associations. I decided that it would be easier when I add more classes that they will be subclasses of Work instead of having all of the types of works as totally separate classes.
Now I'm wondering if I could write the associations ALL of the works would have in the Work model, with all of the sub-classes keeping all of those associations as well.
Sorry if it's a bit confusing. Let me know if you need clarification or if you need to see my code, or if you need any other information.
Not only should you define associations in Work (super) class (and they will be usable in subclasses), you should also use works table for all the subclasses of Work.
It's typical case of Single Table Inheritance.
To enable it, you just need to add type column to works table (add migration for it).
When you query for Tales, for instance, Rails creates the following SQL query:
SELECT * FROM WORKS WHERE type='Tale';
There is a great (and consice) writeup about this concepts in Rails documentation: http://api.rubyonrails.org/classes/ActiveRecord/Inheritance.html

How can I get a list of the associated models that were updated during the save of an instance of ActiveRecord?

I'm passing params to a model instance and saving it with update_attributes. It is associated with several other models and I've configured it to update some of these with accepts_nested_attributes_for.
This is very nice and clean as I only have to update the one model, but I'd like to get a list of the associated(nested) models that were also updated so that I can give the user feedback about some of the fields that have changed.
Is there a way to do this, or am I approaching the problem in the wrong way?
I've found a solution to my question, maybe not the best one but it will work.
For a list of models that are associated and have accepts_nested_attributes_for configured we go:
associations = ModelClass.reflect_on_all_autosave_associations()
Each of these association objects has a name attribute(the association name), which can be used to access the association on the instance, and then we can check whether this association has changed:
associations.each{|assoc|
model_instance.send(assoc.name).changed?
}
It should be noted that we cannot use update_attributes with this solution, as all the models are saved before we can check whether anything has changed. So we have to assign_attributes and save the model in separate steps:
model_instance.assign_attributes(params[:model_instance])
// check for changes on associations here
model_instance.save()

How to structure this so I get all the benefits from STI with none of the consequences? (Pretty irresponsible, I know.)

Say I have the following example of associations in a Rails app:
I'm considering combining the *Posting models under STI. One problem with STI is the potential for many attributes that are only related to one subclass (i.e., a lot of denormalized nil values). This is especially worrisome when your subclasses and going to evolve and grow in the future. I've read a few related posts (such as this), however, as you can see in my example, the potential subclass-specific fields will not necessarily be just attributes, but rather, a lot of belongs_to associations.
My question is, how could I restructure this to use STI from a Posting model for all the common attributes/methods (of which there will be quite a few in my actual app), but keep the unique subclass-specific attributes and belongs_to associations from piling up in the Posting model? Also, the ability to access #board.postings and work with those standard methods is important.
For example, I've thought about moving the type-specific attributes to another model:
class CarPosting < Posting
has_one: car_posting_detail
end
class CarPostingDetail < ActiveRecord::Base
belongs_to :car_posting
belongs_to :car_make
belongs_to :car_model
end
Although, this starts to create a lot of joins, I'm not sure I have the has_one/belongs_to declarations in the right direction, and you have to start chaining calls (e.g., #posting.car_posting_detail.car_make).
Are there other design patterns you have seen for accomplishing this?
You basically have to 2 options for accomplishing inheritance.
First, you can use rails STI as you suggested. The downside is that end up with nil attribute for the child classes that do not use all of the fields. Your idea to reduce this by adding type-specific attributes to another model is a great way to reduce this. However, you should keep the implementation as DRY as possible by defining a has_one :detail for the Posting. Then you can simply assign specific detail types in the Posting childs. For example, CarPosting's detail would be CarPostingDetail. This is convenient because then all Posting children will have their details accessed identically, but will still have different details. So the query now looks like #posting.detail.car_make. To take this one step further, you can define a custom helper method in your Posting model to grab each attribute in the current Posting's detail and create an accessor for it. Now the entire detail layer is transparent and you can simply access those attributes by saying #posting.car_make.
Second, you can use an abstract class. This is essentially the reverse of STI. You create an abstract model class which can never be instantiated. Thus, you cannot define any relationships in the Posting class because it has no table. Each child of the abstract Posting class has its own separate table. The main advantage of doing this would be the ability to define methods for all of your Posting types without copy and pasting them into every model. So this options is better if there are some overlapping functionality across the models, but very little data overlap.
You could use polymorphic associations for this.
Post model belongs_to :postable, :polymorphic => true
car, event and all the other "postable" classes would have this relationship
has_many :posts, as: :postable
Post would hold the postable_id and postable_type
More info here
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

Resources