Even though I know that, at least to my knowledge, this is not the default way of doing associations with ActiveRecord I'm looking for help in order to implement an "hibernatish" Polymorphic model.
For instance, consider the following base model:
# Content only has one property named path
class Content < ActiveRecord::Base
self.abstract_class = true
end
And the concrete content:
# Audio only has one property on it's own (duration).
# However, it should also inherit Content's property (path)
class Audio < Content
end
Now, something relatively interesting happens when using ActiveRecord, more accurately Rails 3 beta 3 ActiveRecord. If you set the abstract_class to false on the Content model and you execute the following:
Audio.create!(:path => '/dev/null')
It kinda works from an Hibernate perspective. That is, a Content record is created with ID 1 and an Audio record is also created with the ID = 1.
However, the problem #1 is that in order for this to work, you obviously need to turnoff the abstract_class, which kinda breaks the whole point of this abstract content example.
Furthermore, the problem #2 is that if you turn on the abstract_class you lose the content's properties when creating instances of Audio. And, if you turn it off, you lose the Audio properties when creating an instance of Audio.
Ideally, when faced with an abstract class that is then subclassed, ActiveRecord would provide the abstract + concrete properties to the concrete class being instantiated, in this case Audio. Effectively, that way, when creating an instance of Audio we would have:
audio = Audio.new #=> <Audio id: nil, duration: nil, path: nil, created_at: nil, updated_at: nil>
And then [naturally] when you assign audio.path = '/dev/null' and performed a save operation, ActiveRecord would know that the path attribute has been inherited, thus needs to be persisted at the parent class level. Furthermore, on that same save operation, should you set a non inherited property of audio ActiveRecord would also persist those changes in the audios table.
My question, after this introduction, is how one could go around active record and enhance it that way?
Effectively, let's assume that we're developing a gem that aims to provide active record with this kind of functionality. How would you go about and do it?
PS: I'm actually considering to develop such gem, though such hackery shouldn't go by without prior thinking. So, your feedback is most welcomed.
Best regards,
DBA
I see what you're going for here, but unfortunately I don't think you can get this to work out of the box the way you'd like.
Basically ActiveRecord uses model inheritance for two things:
Abstract classes, from which subclasses can inherit code and behavior but NOT table structure. You could implement a #path attribute on Content using attr_accessor and it would be inherited by subclasses, but it would not be persisted to the database unless the table for your model subclass had a 'path' column.
Single table inheritance, where subclasses inherit both behavior and table persistence.
You can mix the two, but there is no scenario where an ActiveRecord model can inherit a persistable column from an abstract class. There's always a 1:1 mapping between persisted attributes and table columns.
Related
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/
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
I'm having problems with AR trying to build associations of models that inherit from others. The problem is the associated models are being saved to the database before the call do the save method.
I found more information in this page http://techspry.com/ruby_and_rails/active-records-or-push-or-concat-method/
That's really weird, why would AR automatically save models appended to the association (with << method) ? One would obviously expect that the save method must called, even if the parent already exists. We can prevent this calling
#user.reviews.build(good_params)
but this would be a problem in a context where the association have an hierarchy, for example: if a Hunter has_many :animals, and Dog and Cat inherit from Animal, we can't do
#hunter.dogs.build
#hunter.cats.build
instead we are stuck with
#hunter.animals << Cat.new
#hunter.animals << Dog.new
and if the Cat/Dog class has no validations, the object will be saved automatically to the database. How can I prevent this behaviour ?
I found out that Rails 3 doesn't fully support associations with STI, and usually hacks are needed. Read more on this post http://simple10.com/rails-3-sti/. As mentioned in one of the comments, this issue is referred in rails 4 https://github.com/rails/rails/commit/89b5b31cc4f8407f648a2447665ef23f9024e8a5
Rails sux so bad handling inheritance = (( Hope Rails 4 fixes this.
Meanwhile I'm using this ugly workaround:
animal = #hunter.animals.build type: 'Dog'
then replace the built object, this step may be necessary for reflection to workout (check Lucy's answer and comments)
hunter.animals[#hunter.animals.index(animal)] = animal.becomes(Dog)
this will behave correctly in this context, since
hunter.animals[#hunter.animals.index(animal)].is_a? Dog
will return true and no database calls will be made with the assignment
Based on Gus's answer I implemented a similar solution:
# instantiate a dog object
dog = Dog.new(name: 'fido')
# get the attributes from the dog, add the class (per Gus's answer)
dog_attributes = dog.attributes.merge(type: 'Dog')
# build a new dog using the correct attributes, including the type
hunter.animals.build(dog_attributes)
Note that the original dog object is just thrown away. Depending on how many attributes you need to set it might be easier to do:
hunter.animals.build(type: 'Dog', name: 'Fido')
Is there a specific naming convention for a model that will not be stored in the database? For example, i have a schedule which will be a model, but will not be in the database because it is just a data structure. In other words it will not extend ActiveRecord::Base?
I view this as an internal implementation detail; I wouldn't reflect it in the name, because other models that are interacting with a given model should not know or care whether it is persisted. And your requirements could change later and it would become persisted.
If you want to create a class in the Model that doesn't persist to the database than just don't inherit ActiveRecord::Base
e.g
class SomeClass
end
The class definition is still saved to the file some_class.rb in the model directory
As for naming convention. Well a Model class is a Model class, it doesn't matter if it persists to the database, or some place else, or not at all. I see no need for any special naming convention.
Your Controller and Views should just interact with the your Model objects without being concerned about that object's underlying persistence mechanism. That's one of the main advantages of Model-View-Controller ... The Controller and View need not be concerned with the inner working on the Model objects. So neither should your naming convention.
I tend to make non-active record models to inherit from the NonActiveRecordModel class (or something like that, which you can define yourself). The abstract NonActiveRecordModel class can have common behavior that is used by all of your non-active record models, such as validations via validatable gem, etc.
I am using single table inheritance in my rails application, and want to explicitly set the type of an instance.
I have the following;
class Event < ActiveRecord::Base
class SpecialEvent < Event
which is implemented through single table inheritance.
SpecialEvent.new works as expected, but I want to be able to do things like
Event.new(:type => 'SpecialEvent')
So I can create different sub_types easily in the application.
However this doesn't work and seems to set :type to nil, not the value I set it to; I suspect this is because by calling Event.new it is overwriting the :type argument.
Has anyone got a good way of doing this?
If you're trying to dynamically instantiate a subtype, and you have the type as a string, you can do this:
'SpecialEvent'.constantize.new()
from "Pragmatic - Agile Web Development with rails 3rd edition", page 380
There’s also a less obvious constraint (with STI). The attribute type
is also the name of a built-in Ruby method, so accessing it directly
to set or change the type of a row may result in strange Ruby
messages. Instead, access it implicitly by creating objects of the
appropriate class, or access it via the model object’s indexing
interface, using something such as this:
person[:type] = 'Manager'
man, this book really rocks
No, I want to create instances of
sub-types, where I want to
programmatically determine which
sub_type they are
– HermanD
You could use a factory pattern, although I have heard recently that people frown on the overuse of this pattern. Basically, use the factory to create the actual types you want to get
class EventFactory
def EventFactory.create_event(event_type)
event_type.constantize.new()
end
end
To me it sounds like you'll need some mojo in the event#create action:
type = params[:event].delete(:type)
# check that it is an expected value!!!
die unless ['Event', 'SpecialEvent'].include(type)
type.constantize.new(params[:event])
Apparently, Rails does not allow you to set Type directly. Here's what I do...
klass_name = 'Foo'
...
klass = Class.const_get(klass_name)
klass.new # Foo.new
I believe .constantize is a Rails inflector shortcut. const_get is a Ruby method on Class and Module.
Up front I'll agree that STI is often NOT the best way to deal with things. Polymorphism, yes, but it's often better to use a polymorphic association than STI.
That said, I had a system in which STI was important. It was a judicial system and things like court cases were remarkably similar across their types and generally shared all their essential attributes. However, a civil case and a criminal case differed in the elements they managed. This happened at several levels in the system so abstracted my solution.
https://github.com/arvanasse/sti_factory
Long story short, it uses a factory method to leverage the common approach described above. As a result, the controller can remain neutral/ignorant of the particular type of STI class that you're creating.
You can use the Rails safe_constantize method, which will ensure the object/class actually exists.
For example:
def typeify(string)
string.classify.safe_constantize
end
new_special_event = typeify('special_event').new