Rails association, nested forms and STI - ruby-on-rails

Here are my simplified models
class Offer < ApplicationRecord
has_many :rooms
end
class Room < ApplicationRecord
belongs_to :offer
end
class Kitchen < Room
end
I'm using STI for Kitchen, cause it seemed the right way to express what I wanted to do (I'm probably wrong).
I want to be able to create an Offer with Rooms in it. I have 'regular' rooms that are instances of Room directly, and more specified rooms such as a Kitchen which can have an extra attribute.
I'm using cocoon to create nested form, it works great to create an offer and add regular rooms. But how can I add kitchens ?
Maybe it's an architecture issue more than an implementation issue. How would you manage to do something like this ?

One solution would be to add JSONB column data to your rooms table. Then you can use jsonb_accessor gem to elegantly manage STI.
I assume you have type column already so you can do something like:
Kitchen.create(offer_id: 1, name: "Great Kitchen", description: "It is great!")
Notice: description would be attribute of JSONB data column. In addition you can create indices for JSONB attributes and even do queries. From my experience, you want to keep JSONB as place to store additional info and less for heavy queries.
I hope this helps.

Related

Best relational model when you want STI but you know you shouldn't?

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.

Iterate on SQL group by in Rails

Say I have the following models with associations:
House belongs_to User
User has_many House
Is there a way that I can do something like using SQL (not ruby group_by)
House.group(:user).each |houses_grouped_by_user|
#Each of these objects would be a set of houses with the same user id
end
Take a look at the docs for ActiveRecord associations.
As your commenter suggested, based on the associations in your model setup, you should lean on the functionality that the associations provide you!
Thus:
user.houses.each do |user_houses|
# do whatever you want to the user_houses
end
If you want to iterate through the houses in larger groups, you can use other iterative methods -- but start with the association and go from there.

Single table inheritance with relations

I'm trying to build a student portal in Rails 3, but I'm having some problem.
The idea is to have a users table that contains all basic data for a given person. See the UML/E-R below for example attributes.
A user can be both an Assistant and a Student at the same time.
Assistant and Student should inherit from User.
The idea was to inherit directly from the User, like this.
class User < ActiveRecord::Base
# ...
def awesome?
[true, false].sample
end
# ...
end
class Student < User
has_one :student
has_many :registered_courses, through: :students
end
Student.new.awesome?
This makes the relations in the student model very strange.
has_many :registered_courses, through: :students
I want to be able to do something like this in the end.
student.full_name
student.pin_code
student.registered_courses
One solution would be to implementing the method by hand, like this
class Student < User
has_one :student
def pin_number
student.pin_number
end
end
But it looks really strange to refer to a student object inside the student model.
Is there a clearer, better way of doing this?
Here is an example UML/E-R. I've tried to keep this example clean by removing non relevant attributes. That is why there are so few attributes in the registered course entity.
STI is not a good choice for this the way that you have articulated it here, since users can be both students and assistants. When you are using STI, you generally add a type column to specify which subclass the record really belongs to. If both Student and Assistant inherit from User, then that really isn't an option, since you'd be forced to create duplicate User records for someone who is both an Assistant and a Student.
I think you'd be better off simply having Student and Assistant rows that belong_to a Student, and then delegating the elements that are contained in User back to the User object.
I feel like Inheritance is a bad move here. If you're going to have STI like this it HAS to be one or the other.
Instead throw all your logic into the User model, all your data is there anyway. Plus since Student & Assistant aren't mutually exclusive there shouldn't be any methods that will override each other.
Why not STI?
STI is mainly meant for objects that contain the same data, but does different things with them.
For example, I have a specification that contains multiple processes(ex. build and test). So I have a order that contains processes.
process_1:
order_id: 1
specification: foo
type: build
process_2:
order_id: 1
specification: foo
type: test
In this example the only thing that changes in the data is the type, but because the type changes I know what process to perform from the specification.

Can I override what an association method returns?

In Rails, I want to override the behavior of an association. For example, by default, if Person has_many :hats, calling some_person.hats would do a simple join using person.id and hat.person_id.
I want to modify that query to include some other criteria. For example, maybe a person's collection of hats should be just the hats that are appropriate to their country.
It seems that I could do something like this:
class Person < ActiveRecord::Base
has_many :hats, :through => :country do
# John lives in Canada, so he gets a baseball cap and a hockey helmet
self.country.hats
end
end
Can I control what an association returns like this? If not, would a scope be the best solution?
I know this is a silly example, but explaining the domain logic that I need this for would be way too boring for everyone here. :)
Scopes are probably your best option because they're chainable and reusable outside your association. Otherwise, you could use association extensions. Check out this thread for more info. Association Extensions

Rails active record model relationship - one model belonging to three models

I have situation where a single model needs to have three foreign ids. This table has the information which belongs to three models.
e.g. -
Three models - Keyboard, Mouse, Monitor
Now i have fourth model details - which has information about what will happen for any combination like keyboard1, mouse1 and monitor1.
So what should be good design pattern for this ?
Right now what i use is Detail.find_by_keyboard_id_and_mouse_id_and_monitor_id(keyboard1id, mouse1id, monitor1id) #=> Detail1
Which of course is not a best way to go...
I don't think what you have is a bad design. There probably aren't too many options for how this should be implemented, but you should think about how you want to use it. Personally, I wouldn't call the model Detail since it doesn't tell you anything about what the model really is. Something like HardwareConfiguration is more explicit. Maybe too lengthy, but you could shorten it to HardwareConfig or Configuration, depending on your taste.
This model should have an id and the three foreign keys. You should add database indexes to each foreign key field as well.
class Detail < ActiveRecord::Base
belongs_to :keyboard
belongs_to :mouse
belongs_to :monitor
end
Then each hardware model would have_many :details like:
class Keyboard < ActiveRecord::Base
has_many :details
end
You could look up the detail combo by its ID, or any combination of foreign keys.
Detail.find(id)
Detail.find_all_by_keyboard_id(keyboard_id)

Resources