Polymorphic relationship vs STI vs Class table inheritance with RoR - ruby-on-rails

There are three types of invoice items with following tables
1) SubscriptionItems,
2) Prorations,
3) UsageItems,
Those have the same attributes below
invoice_id
amount
stripe_invoie_id
However
only SubscriptionItem and Proration
period_start_at
period_end_at
and only Proration and UsageItem has
title
and only UsageItem has
uuid
account_id
description
To achieve this model I've been using polymorphic relation.
class InvoiceItem < ActiveRecord::Base
belongs_to :invoice
belongs_to :itemable, polymorphic: true
end
class SubscriptionItem < ActiveRecord::Base
belongs_to :plan
has_one :invoice_item, as: :itemable
end
class UsageItem < ActiveRecord::Base
belongs_to :account
has_one :invoice_item, as: :itemable
end
class Invoice < ActiveRecord::Base
belongs_to :account
has_many :invoice_items
end
class Account < ActiveRecord::Base
has_many :invoices
has_many :usage_items
end
For now it works.
However As far as I understand polymorphic should have has_many relation.
So this resides in the middle of Polymorphic and STI.
Because those three types of invoice items are always be subscriptionitem, proration, or usageitem.
It's hard decision that I could keep using this models (polymorphic with has_one) or should I use STI instead?
Or class table inheritance should be fit?
EDIT
I'd love to hear the reason why I could use some design.
Maybe those types pros and cons.
As far as I know,
If I apply STI
That leads many NULLable columns, but RoR supports STI. So it's easy
to use.
If I apply polymorphic with has_one
It stills the rails way but the original polymorphic definition is
different. It should have has_many relationship instead of
has_one. Also it's impossible to add foreign key.
Ref: Blog post for STI to polymorphic
If I apply Class table inheritance,
It's more efficient for relational database, but it's not rails way.
Ref: Blog post for STI to class table inheritance

I think STI with a hidden_field passing the appropriate value for each attribute that should determine the invoice type, could be the way to go here. It's simple and efficient.
Let's say you added a field called :invoice_type to your invoice model,
Then just loop through the items in an array like (Rough example):
<% #invoices.where(:invoice_type => "proration").find_each do |invoice| %>
<% #invoices.where(:start_date => "#{#invoice.start_date}").find_each do |invoice| %>
<!--Will only show the start_date of invoices where the invoice_type is propration. -->
<% end %>
<% end %>

Related

Rails ActiveRecord equivalent of Laravel ORM `attach()` method for polymorphic has_many :through

I'm transitioning from "Laravel ORM" to "Rails Active Record" and I couldn't find how do you do something like this:
$this->people()->attach($person['id'], ['role' => $role]);
Explanation for Laravel code snippet
People is a polymorphic association to the class that is being accessed via $this via the Role class. The function above, creates a record in the middle table (roles/peopleables) like this:
id: {{generically defined}}
people_id: $person['id']
role: $role
peopleable_type: $this->type
peopleable_id: $this->id
How the association is defined on the Laravel end:
class XYZ {
...
public function people()
{
return $this->morphToMany(People::class, 'peopleable')->withPivot('role','id');
}
...
}
My efforts in Ruby
Here is how I made the association in Ruby:
class Peopleable < ApplicationRecord
belongs_to :people
belongs_to :peopleable, polymorphic: true
end
class People < ApplicationRecord
has_many :peopleables
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
I have seen the operation << but I don't know if there is any way to set an additional value on the pivot table while triggering this operation. [in this case the roles or peopleables tables; I use these two terms interchangeably in this app.]
PS. So, basically the question is how to define additional values on the pivot table in a polymorphic-many association in ActiveRecord and dynamically set those values while initiating an attachment relationship
Description of Functionality
Our application has a limitless [generally speaking, not that there is no computational limits!] content type: post, novel, poem, etc.
Each of these content types can be associated to individuals who play certain roles: editor, author, translator, etc.
So, for example:
X is the translator of Post#1. X, Y and Z are authors of Post#1.
There is a distinct People model and each content type has its own unique model [for example: Post, Poem, etc].
The idea of :through is referring to the 'Role class' or 'the pivot table' [whichever way you want to understand it] that the polymorphic association is recorded on it.
In addition to the information regarding a simple polymorphic relationship, there is also the kind of role that is recorded on the pivot table.
For example, X is both the author and the translator for Post#1, so there are two rows with the same people_id, peopleable_type and peopleable_id, however they have different values for role.
From what I understand given your description, I think you have this models (I'll change the names to what I understand they are, hope it's clear enough):
class Person < ApplicationRecord # using singular for models
has_many :person_roles
end
class Poem < ApplicationRecord
has_many :person_roles, as: :content
end
class Novel < ApplicationRecord
has_many :person_roles, as: :content
end
etc...
class PersonRole < ApplicationRecord
belongs_to :person
belongs_to :content, polymorphic: true
# you should have a "role" column on your table
end
So a Person is associated to a "content" (Novel, Poem, etc) via the join model PersonRole with a specific role. A Person that is the author of some novel and the editor of some peom would have two PersonRole records.
So, if you have a person and you want to assign a new role on some content, you can just do:
person.person_roles.create(role: :author, content: some_poem)
or
PersonRole.create(person: person, role: :author, content: some_poem)
or
some_poem.person_roles.create(person: person, role: :author)
You have two things in play here: "belongs_to :content, polymorphic: true" is covers the part of this being a polymorphic association. Then you have the "PersonRole" table that covers the part you know as "pivot table" (join table/model on rails).
Note that :through in rails has other meaning, you may want to get all the poems that a user is an author of, you could then have a "has_many :poems, through: :person_roles" association (that won't actually work, it's more complex than that in this case because you have a polymorphic association, you'll need to configure the association with some extra options like source and scope for this to work, I'm just using it as an example of what we understand as a has many :through association).
Rails is 'convention over configuration'. Models' must be in singular 'Person'.
ActiveRecord has has_many ... through and polymorphic association
"Assignable" and "Assignments" are more natural to read than "peoplable"
class Person < ApplicationRecord
has_many :assignments, as: :assignable
has_many :roles, through: :assignments
end
class Role < ApplicationRecord
has_many :assignments
has_many :people, through: :assignments
end
class Assignment
belongs_to :role
belongs_to :assignable, polymorphic: true
end
You can read more Rails has_many :through Polymorphic Association by Sean C Davis

Create Join Table for a belongs_to association in Rails 5

I have a products model and a variations model as a belongs_to association. There are some variations that absolutely belong to a single product, but there are others that can belong to many products. Can I create a join table on a belongs_to association like in a has_and_blongs_to_many association?
My Models Currently
product.rb
class Product < ApplicationRecord
has_many :variations, dependent: :destroy
has_and_belongs_to_many :categories
has_and_belongs_to_many :subcategories
include FriendlyId
friendly_id :name, use: :slugged
def should_generate_new_friendly_id?
name_changed?
end
end
variation.rb
class Variation < ApplicationRecord
has_and_belongs_to_many :categories
has_and_belongs_to_many :subcategories
belongs_to :product
include FriendlyId
friendly_id :name, use: :slugged
def should_generate_new_friendly_id?
name_changed?
end
end
From Rails guides association basics - the belongs_to association:
A belongs_to association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model.
When you do the belong_to :product association on the Variation model, it expect to have a field named product_id which will point to the associated product.
use example:
variation = Variation.first
product = variation.product # this line will get the product which is associated to the variation by the product_id column.
Since it can hold only one integer (one product id) the best option is to restructure your code. It makes no sense to use a "belong_to" as "has_many" association.
You need to change the association to some king of many to many association.
To chose the best option for you, read and learn the differences in the Rails guides - Association Basics
*** Make sure you won't lose your data when changing the association:
Idea of doing that:
Create the join table
Copy the info from your variations table (the variation.id and the associated product_id)
Start using the new association
(You can probably copy the data in the migration file, just search how to do it)

Modelling Many Rails Associations

I'm trying to wrap my head around how I should model my database for a parent model with many has_one associations (20+). I have one model called House which is the parent model of all the other models:
class House < ActiveRecord::Base
has_one :kitchen
has_one :basement
has_one :garage
has_one :common_room
#... Many other child models
end
All the child models contain unique properties specific to their own class. I've thought about STI, but there isn't really any shared functionality or inputs that I can use across models. I've also thought of making one 'super model', but that doesn't really follow Rails best practices, plus it would contain over 200 columns. Is there another design pattern or structure that I can use to model this efficiently so that I can reduce database calls?
class House
has_many :rooms
Room::TYPES.each do |room_type|
has_one room_type, -> { where(room_type: room_type) }, class_name: "Room"
end
class Room
belongs_to :house
TYPES = %i/kitchen basement garage common_room etc/.freeze
end
In your migration make sure to add_index :rooms, [:type, :house_id], unique: true. Unless a house can have more than 1 type of room. If that is the case, I think a different approach is needed.
To your second question, it depends really, what type of database are you using? If its PostgreSQL you could use hstore and store those as a properties hash. Or you could serialize you db to take that as a hash. Or you could have another model that room has many of. For instance has_many :properties and make a property model that would store that information. Really depends on what else you want to do with the information
Why not using Polymorphism in Rails? It would be much simpler
class House < ActiveRecord::Base
has_many :properties, as: :property_house
end
class Kitchen < ActiveRecord::Base
belongs_to :property_house, polymorphic: true
end
class Garage < ActiveRecord::Base
belongs_to :property_house, polymorphic: true
end
For more information, go here: http://terenceponce.com/blog/2012/03/02/polymorphic-associations-in-rails-32/

rails - why polymorphic associations

An example from this blog
class Tag < ActiveRecord::Base
attr_accessible :name, :taggable_id, :taggable_type
belongs_to :taggable, :polymorphic => true
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags, :as => :taggable
end
It looks to me we could do thing like this without polymorphic associations
class Tag < ActiveRecord::Base
attr_accessible :name,
belongs_to :cars,
belongs_to :bikes,
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :tags
end
class Bike < ActiveRecord::Base
attr_accessible :name
has_many :tags
end
What is the difference of with polymorphic associations and without?
THanks
In your last example, Car has many tags, so conventionally the "tags" table will have a field car_id. Now Bike has many tags, so one more field bike_id.
Without Polymorphic, how many fields are you going to create for the tags table? :)
Even you guarantee that there will only be two models have tag ultimately, there will also lots of null data in the table, say a bike doesn't have car_id, and that is not nice.
Polymorphism solved this problem by defining a common interface so that Car and Bike can share same operations with different sub type. http://www.princeton.edu/~achaney/tmve/wiki100k/docs/Polymorphism_in_object-oriented_programming.html
A polymorphic association allows you to create a table with relationships between multiple models.
Consider your example. Both Car and Bike can have many tags, so instead of creating two different tables, say car_tags and bike_tags, you can use a single polymorphic table named Tag which not only stores the foreign key (in a column named resource_id), but also the type of resource it's associated with (in a column named resource_type), which, in this case would be Car or Bike.
In summary, polymorphic relationships are between many different models whereas normal relationships are, generally speaking, only between two.
You can find more information in the RoR Guides; http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Hope that helps clarify things a bit.
Polymorphic associations allow for a single model to belong to multiple models on a single association[1]. Taking your example above, simply adding has_many :tags to the Car and Bike models and belongs_to :car and belongs_to :bike to the Tag model has two major shortcomings:
It introduces a glut of foreign keys of which a large quantity will have no value
It still doesn't allow for a tag to belong to more than one model
Some great resources for learning more about polymorphic associations are listed below.
RailsGuides on Polymorphic
Associations
RailsCasts #154 Polymorphic Associations
(revised)
What's the Deal with Rails' Polymorphic
Associations?

Action Controller: Exception - ID not found

I am slowly getting the hang of Rails and thanks to a few people I now have a basic grasp of the database relations and associations etc. You can see my previous questions here: Rails database relationships
I have setup my applications models with all of the necessary has_one and has_many :through etc. but when I go to add a kase and choose from a company from the drop down list - it doesnt seem to be assigning the company ID to the kase.
You can see a video of the the application and error here: http://screenr.com/BHC
You can see a full breakdown of the application and relevant source code at the Git repo here: http://github.com/dannyweb/surveycontrol
If anyone could shed some light on my mistake I would be appreciate it very much!
Thanks,
Danny
You have setup your Kase and Company models as a one-to-one relationship (see http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html). This is probably not what you intended. Maybe if you could explain your intended relationship I could tell you where your mistake is?
class Company < ActiveRecord::Base
has_many :kases
has_many :people
end
class Kase < ActiveRecord::Base
belongs_to :company # foreign key: company_id
has_and_belongs_to_many :people # foreign key in join table
end
class Person < ActiveRecord::Base
has_and_belongs_to_many :kases # foreign key in join table
end
Relevant parts shown only. This should be a step in the right direction. You will need a join table for the many-to-many relationship, or alternatively, to model it using "has_many :through". Depends on whether you need to store other properties on the join. See link above for details.
I believe It should be
class Company < ActiveRecord::Base
has_many :people
has_many :kases
end
class Kase < ActiveRecord::Base
belongs_to :company
belongs_to :person
end
class Person < ActiveRecord::Base
belongs_to :company
has_one :kase
end
In your view (app/views/kases/new.html.erb) you have
<li>Company Select<span><%= f.select :company_id, Company.all %></span></li>
Try changing the select part to
<%= f.select :company_id, Company.all.collect {|m| [m.name, m.id]} %>
Suggestion
I also notice that you have four methods in your controller to find Kases by status. You can do this in your model, using named_scope. It's like this:
named_scope :active, :conditions => {:kase_status => 'Archived'}
And then, wherever you need to show only active Kases, you call Kase.active. The same for the other status.

Resources