In Rails 4.2 I need some kind of inheritance for this structure:
User has many Reports, Report belongs to User
ImpactReport is a Report
ChallengeReport is a Report
PlanningReport is a Report
StImpactReport is an ImpactReport
I've read about single-table inheritance supported by Rails, but I don't think it would let me go two levels deep on inheritance, also StImpactReport has a bunch of fields that are not in ImpactReport
I've also seen a lot of articles about how to implement muti-table inheritance, but they all seem quite complex, when in my head there's a much simpler way to do it.
I think it could be done simply with the belongs_to relationship in conjunction with delegate and accepts_nested_attributes_for like this:
class Report < ActiveRecord::Base
end
class ImpactReport < ActiveRecord::Base
belongs_to :report
accepts_nested_attributes_for :report
delegate :user, to: :report
delegate :other_report_field, to: :report
end
and so on...
The advantages of this are:
I have separate tables for each model, but only the fields specific to that model are in their tables, common fields are in the table of the parent.
I can go as many levels deep as I like
I can have, say, an impact_report and a challenge_report that both share the same report attributes. When the attribute is changed in one the other is also changed. (this is good for my app)
Disadvantages:
It doesn't use class inheritance so methods of parents are not accessible to the children
My questions are these:
Are there any other disadvantages to this way of doing inheritance that I haven't thought of? Will it limit my application in some unforeseen way?
If I have a report object, what is the best way for me to access all the attributes of the children of this object?
If doing this structure, would it be better to use has_one instead of belongs_to?
Related
I'm looking to understand what would be the best Relational Model for a Rails app.
It sounds like Single Table Inheritance would "work" to a degree, but not really when I consider how varied the Child Models would be in terms of their "Schema" and fields.
The situation, would be something where I have lots of different types of Appraisal, and I want the flexibility to do stuff like Appraisals.first.type = CarAppraisal or Appraisals.count = 142, where that count would be the sum of multiple different sub-model classes of Appraisal (eg HouseAppraisal, CatAppraisal).
Master Model Type: Appraisal
class Appraisal < Application Record
has_one :car_appraisals
has_one :house_appraisals
has_one :life_appraisals
#insert other has_one _appraisals I might add in the future
end
Child Model Type: House Appraisal
class HouseAppraisal < Application Record
belongs_to :appraisal
end
Child Model Type: Car Appraisal
class CarAppraisal < Application Record
belongs_to :appraisal
end
etc etc etc
What I've Considered
I thought of something where I could utilise a has_many, through: xxx but I can't really put my finger on how this might work?
I know I put has_one in the Parent Appraisal model, and that isn't quite correct given it implies it would contain one of each, when it fact it would be only one of them.
To complicate things, I'm actually mapping the Appraisal model across ActiveRecord (Postgres) and the Child models (eg PetAppraisal) mapped across MongoId (MongoDB), given they vary a fair bit in schema, but I don't think that should really change much in terms of the overall ERD.
I'm able to get the behavior I'd like if I create a Foo class, some STI classes that inherit it (Foo1, Foo2, etc.) and then to add different has_many :bars associations to each class. For example:
class Foo < ApplicationRecord
has_many :bars
end
class Foo1 < Foo
has_many :bars, -> { where(user_id: 1) }
end
I want to be able to call bars on an object and get a different association behavior depending on the state of the object. This works, but is there a way to do it without setting up STI?
I tried doing everything inside foo.rb but I seem to be loading my first has_many :bars definition even if I do something like this:
has_many :bars ... if some_method_returning_boolean?
has_many :bars ... if some_other_method_returning_boolean?
And even if this did work, it seems kind of clunky.
I also considered scopes, but as far as I understand scopes then I'd have to call foo.bars.something and foo.bars.something_else instead of relying on the state of the object foo to give me foo.bars differently. Also, it looks like scopes only cover part of a has_many definition but can't modify arguments like :source, :foreign_key, but I may be wrong.
Is there another way?
STI is the correct way of doing this. It's generally understood that any particular model class should have well-defined and consistent relationships with other records.
If you have relationships that only apply to certain kinds of records that's exactly what STI is for: Create a subclass that defines these relationships.
You see this all the time. An example would be that both "Parent" and "Student" are of base type "Person", but that the students have a belongs_to: parent association and the parents have a has_many: children relationship.
Of course this presumes parents are unlikely to become students, and students parents, an assumption that may not be correct. Whatever assumptions you make, I hope there's some serious thinking about if these restrictions are relevant or just overly paranoid.
It's generally bad form to switch types on a record even though it can technically be done. You could use this to adjust how a record's relationships are defined if you think this is strictly necessary.
Generally I'd advise you to stick with a consistent relationship structure. Those records which should not be related to anything don't need those methods physically removed, they can be there while not doing anything useful. They come along for free anyway.
Maybe you will be satisfacted by overwrite your bars method in this way ?
This super just return usual result from something_foo.bars.
class Foo
def bars
super.where(something: something_value) if your_condition
super.where(something_else: something_value) if other_condition
end
end
I understand the concept of relational databases, primary/foreign keys, etc, however, I'm having trouble seeing the actual result of setting these properties up in my models. Do they generate helper functions or something similar? or do they simply work on a database level?
For example, if I have these two models (other properties omitted)
class Course < ActiveRecord::Base
has_and_belongs_to_many :schedules
has_many :sections
end
class Section < ActiveRecord::Base
belongs_to :course
end
I could simply get all sections for any given course like this:
Section.where(course_id: 1234)
However, I could do this without having set up the relations at all.
So my question is: Why do we do this?
Adding these methods let's you do things like this:
Section.find(5).course # <== returns a 'Course' model instance
Also it let's you join your queries more easily:
Section.joins(:course).where(course: {name: "English"}) # <== returns sections who have the 'english' course
Neither of these would be possible if you didn't set up the relations in the model.
Similarly:
Course.find(8).sections # returns an array of sections with 'course_id = 8'
It makes your calls more semantic, and makes things easier from a programmatic perspective :)
Relations are applied on instances of an object. So, these relations allow you to get related objects to an instance of another.
For example, say you have an instance of Section (called #section). You'd be able to get all Course objects for that section by doing:
#section.course if you have belongs_to :course set up.
Similarly, if you have an instance of Course, you can get all Section objects for that Course with:
#course.sections if you have has_many :sections.
TL;DR - these are helper scopes for instance variables of Course and Section.
I have a Change model that utilizes single table inheritance that has the following attributes:
id
type #this is a single table inheritance type field.
description
dynamic_id
I also have two sub classes, Race which is a subclasses of Change and Workout which is a sub class of Race.
class Race < Change
end
class Workout < Race
end
I have a fourth class called Track and I'd like to create the following four associations by just using the dynamic_id field in the Change object. (i.e. I have not explicitly added race_id and workout_id to the Change table. Instead I want to use the dynamic_id as the race_id for the Race class and the dynamic_id as the workout_id for the Workout class) By doing this, I will avoid having a lot of nil fields in my database.)
Here are the four associations I'm trying to create.
Race Model - belongs_to :track
Workout Model - belongs_to :track
Track Model - has_many :races
Track Model - has_many :workouts
I've been trying to accomplish this with associations using :class_name and :foreign_key, but I can't seem to get it working. Is this actually possible. I realize its probably not a best practice, but I'd still like to see if it doable. Thanks for your input.
What you are looking for are "polymorphic associations". You can find more in the rails guides: http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Your case is a bit special because you want to use polymorphic associations with STI. I remember that there was a bug with this combination but it could be fixed by now.
I did not read it completely but this blog post seems to describe the situation: http://www.archonsystems.com/devblog/2011/12/20/rails-single-table-inheritance-with-polymorphic-association/
The problem I encountered with polymorphic associations and STI is described here: Why polymorphic association doesn't work for STI if type column of the polymorphic association doesn't point to the base model of STI?
I am working on a very large Rails application. We initially did not use much inheritance, but we have had some eye opening experiences from a consultant and are looking to refactor some of our models.
We have the following pattern a lot in our application:
class Project < ActiveRecord::Base
has_many :graph_settings
end
class GraphType < ActiveRecord::Base
has_many :graph_settings
#graph type specific settings (units, labels, etc) stored in DB and very infrequently updated.
end
class GraphSetting < ActiveRecord::Base
belongs_to :graph_type
belongs_to :project
# Project implementation of graph type specific settings (y_min, y_max) also stored in db.
end
This also results in a ton of conditionals in views, helpers and in the GraphSetting model itself. None of this is good.
A simple refactor where we get rid of GraphType in favor of using a structure more like this:
class Graph < ActiveRecord::Base
belongs_to :project
# Generic methods and settings
end
class SpecificGraph < Graph
# Default methods and settings hard coded
# Project implementation specific details stored in db.
end
Now this makes perfect sense to me, eases testing, removes conditionals, and makes later internationalization easier. However we only have 15 to 30 graphs.
We have a very similar model (to complicated to use as an example) with close to probably 100 different 'types', and could potentially double that. They would all have relationships and methods they inheritated, some would need to override more methods then others. It seems like the perfect use, but that many just seems like a lot.
Is 200 STI classes to many? Is there another pattern we should look at?
Thanks for any wisdom and I will answer any questions.
If the differences are just in the behavior of the class, then I assume it shouldn't be a problem, and this is a good candidate for STI. (Mind you, I've never tried this with so many subclasses.)
But, if your 200 STI classes each have some unique attributes, you would need a lot of extra database columns in the master table which would be NULL, 99.5% of the time. This could be very inefficient.
To create something like "multiple table inheritance", what I've done before with success was to use a little metaprogramming to associate other tables for the details unique to each class:
class SpecificGraph < Graph
include SpecificGraphDetail::MTI
end
class SpecificGraphDetail < ActiveRecord::Base
module MTI
def self.included(base)
base.class_eval do
has_one :specific_graph_detail, :foreign_key => 'graph_id', :dependent => :destroy
delegate :extra_column, :extra_column=, :to => :specific_graph_detail
end
end
end
end
The delegation means you can access the associated detail fields as if they were directly on the model instead of going through the specific_graph_detail association, and for all intents and purposes it "looks" like these are just extra columns.
You have to trade off the situations where you need to join these extra detail tables against just having the extra columns in the master table. That will decide whether to use STI or a solution using associated tables, such as my solution above.