"has_two" model relationship with polymorphic model? - ruby-on-rails

I have a polymorphic PLACE model like this:
class Place < ActiveRecord::Base
belongs_to :placeable, polymorphic: :true
...
So, for example, in my Lodging model I have something like:
class Lodging < ActiveRecord::Base
has_one :place, as: :placeable, dependent: :destroy
...
And they work as expected.
The point is that now I want to create a CarRental model and this model has TWO places, on is the pick-up place and the other one is the drop-off place.
So I wrote:
class Transportation < ActiveRecord::Base
has_one :start_place, as: :placeable, dependent: :destroy
has_one :end_place, as: :placeable, dependent: :destroy
And of course that does not work. Any insights on how to do that?
EDIT 1: IF I DO LIKE
class Transportation < ActiveRecord::Base
has_one :start_place, class_name: 'Place', as: :placeable, dependent: :destroy
has_one :end_place, class_name: 'Place', as: :placeable, dependent: :destroy
It works! But why? Where is the start or end information saved?
** EDIT 2: NOPE, IT DOES NOT WORK **
It does not work... =(

I guess, associations spitted out errors before because of the unknown classes. has_one :start_place by default assumes you mean a class StartPlace that doesn't exist. It singularizes the term (as best as it can) and converts it to CamelCase. Once you've specified that you mean Place, it's clear.
You should be adding a new column anyways.Let's try single table inheritance (STI). Add a column type of type string to your places table:
rails generate migration AddTypeToPlaces type:string
...make sure it does what it says, then migrate and create new models like so:
rails generate model StartPlace --no-migration --parent=Place
rails generate model EndPlace --no-migration --parent=Place
Note: they don't get a dedicated table and inherit from Place, not ActiveRecord::Base. It should generate two empty classes, that's fine, they inherit things from Place.
...then revert your associations to what didn't work a while ago:
has_one :start_place, as: :placeable, dependent: :destroy
has_one :end_place, as: :placeable, dependent: :destroy
...and they should work now, because StartPlace is defined as
Place with type equal to "StartPlace"
...same with EndPlace with corresponding type.
I described this quite some time ago for a similar case.

Your schema doesn't make sense to me - that places belong to lodgings etc. Surely there's a fixed number of places, and then they can have a number of different things in them? In your schema the same place could be in the database lots of times which seems wrong. I don't think that polymorphic associations are the way to go here either.
I would model this like so:
class Place
has_many :lodgings
has_many :starting_transportations, :class_name => "Transportation", :as => :start_place
has_many :ending_transportations, :class_name => "Transportation", :as => :end_place
class Lodging
belongs_to :place #using lodgings.place_id
class Transportation
belongs_to :start_place, :class_name => "Place" #via transportations.start_place_id
belongs_to :end_place, :class_name => "Place" #via transportations.end_place_id
btw, i think "Location" is a better name than "Place" for physical locations in the real world. "Place" sounds too vague.

Related

Rails many to many relationship confusion

So im working on a rails app for users to create events (and attend other created events). You can read about the assignment here (for the Odin Project): https://www.theodinproject.com/courses/ruby-on-rails/lessons/associations
Anyways I thought I had understood many to many relationships in rails, but the way i've seen other people write the models is confusing to me.
To me it seems like it should be something like:
class User < ApplicationRecord
has_many :attendances
has_many :events, through: :attendances
end
class Attendance < ApplicationRecord
belongs_to :user
belongs_to :event
end
class Event < ApplicationRecord
has_many :users
has_many :users, through: :attendances
end
This makes sense to me because a User can create many events, and an event can have many users attending. (Although attendances is probably the wrong word, maybe invites or something).
But i've seen some weird examples (You can see others source code below on the project) and it seems like they are adding much more to the models and also renaming the source/foreign_key/class_name.
Am I missing something? This still allows a user to "own" an event right? Maybe im mis-understanding how many-to-many works. But this fits at least in my mind of how it should be.
For reference some other models I was seeing was similar to this:
class Event < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
has_many :event_attendees, :foreign_key => :attended_event_id
has_many :attendees, :through => :event_attendees
end
class EventAttendee < ActiveRecord::Base
belongs_to :attendee, :class_name => "User"
belongs_to :attended_event, :class_name => "Event"
end
class User < ActiveRecord::Base
has_many :created_events, :foreign_key => :creator_id, :class_name => "Event"
has_many :event_attendees, :foreign_key => :attendee_id
has_many :attended_events, :through => :event_attendees, :foreign_key => :attendee_id'
end
Basically similar things to the above. Im not really sure what this is doing? Or why all the extra is necessary.
In your example everything according to conventions. Maybe except many-to-many table naming.
attendances table has 'user_id' and 'event_id' fields. But in case it could conflict with other fields, or not descriptive enough you could use different keys.
belongs_to :creator, :class_name => "User"
belongs_to :creator by default would look for Creator model, so it is needed to specify class name explicitly, like in the provided example.
has_many :event_attendees, :foreign_key => :attended_event_id
By default foreign key would be event_id, so here it is specified explicitly too.
has_many :created_events, :foreign_key => :creator_id, :class_name => "Event"
By default, rails would look for user_id foreign key and CreatedEvent model. And these attributes specified explicitly.
You just need to understand what attributes rails provides by default, to change if it is required.
ActiveRecord associations default to a class and foreign key with the same name as the association. The code here is specifically specifying these because they are not the default.

Related model can be one of two other models

I currently have essentially 3 models at the moment. I have Project, User and Contact.
I'm trying to assign users or contacts as a sort of 'member' to each project. I initially thought that a linking table here would suffice, for example ProjectMembers but i'm currently hitting a brick wall in my thought process when it comes to a project member only being allowed to be either a Contact or a User and whether to handle this via a relationship or by code in the model or the controller by checking which between user_id or contact_id was not null.
I had a look at polymorphic associations which looked promising, but somehow I ended up with the association backwards (Projects were being entered into the ProjectMember table as the type, rather than User or Contact) and confused myself even more.
The final output I would pretty much like would be simply to have the ability to run something like Project.first.project_members and have those members return with the role in that project. It'd be even nicer if I could run User.first.projects/Contact.first.projects and get those too, but that's something I can figure out down the line.
Any pointers would be greatly appreciated.
Polymorphic association is something that you should go for here.
First: Project has a many to many relationship with User and Contact, hence you need a join table.
Second: Since you need to call something like Project.first.project_members, you should have a polymorphic association.
For this scenario, you should create a polymorphic join table.
Let's say you have the polymorphic table ProjectMember with memberable_id, memberable_type and project_id (well I have a hang of railcasts).
class ProjectMember < ActiveRecord::Base
belongs_to :memberable, :polymorphic => true
belongs_to :project
end
and then in your User model,
class User < ActiveRecord::Base
has_many :project_members, :as => :memberable
has_many :projects, through: :project_members
end
and in Contact model
class Member < ActiveRecord::Base
has_many :project_members, :as => :memberable
has_many :projects, through: :project_members
end
and in your Project model
class Project < ActiveRecord::Base
has_many :users, :through => :project_members, :source => :memberable, :source_type => 'User'
has_many :contacts, :through => :project_members, :source => :memberable, :source_type => 'Contact'
has_many :project_members
end
Now you can call your desired associations, Project.first.project_members, User.first.projects or Contact.first.projects. Hope this helps.
Thanks

Rails 4 has_many through naming

I'm having problems with a Rails 4 join table. I have quite a simple setup which is working elsewhere in my application using a non-conventionally named table for users, groups and usergroupmemberships. I'm trying to set it up this time using the proper conventional naming and it's just not working.
Models involved are User, ManagementGroup and ManagementGroupsUser
db tables: management_groups_user, management_groups, users
app/models/user.rb
Class User < ActiveRecord::Base
...
has_many :management_groups, through: management_groups_users
has_many :management_groups_users
....
app/models/management_group.rb
class ManagementGroup < ActiveRecord::Base
has_many :users, through: :management_groups_users
has_many :management_groups_users
app/models/management_groups_user.rb
class ManagementGroupsUser < ActiveRecord::Base
belongs_to :users
belongs_to :management_groups
The association appears to work from with #user.management_groups_users but nothing else. I'm fairly sure this is a problem with naming / plurality but I can't figure it out.
This is the model which joins the remaining models user.rb and management_group
#app/models/management_groups_user.rb
belongs_to :user
belongs_to :management_group
Since we are going to use model above to access another model management_group then
#app/models/user.rb
has_many :user_management_groups #This should come first
has_many :management_groups, through: user_management_groups
Since we are going to use model above to access another user model then
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
Now should work
Do it this way.
app/models/user.rb
has_many :user_management_groups
has_many :management_groups, through: user_management_groups
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
app/models/management_groups_user.rb
belongs_to :user
belongs_to :management_group
I hope these associations will help you.
This is another way if you pass foreign key and class name.
app/models/user.rb
has_many :user_management_groups, :foreign_key => "key", :class_name => "ClassName"
has_many :management_groups, through: user_management_groups, :foreign_key => "key", :class_name => "ClassName"
app/models/management_group.rb
has_many :user_management_groups
has_many :users, through: :user_management_groups
app/models/management_groups_user.rb
belongs_to :user, class_name: "ClassName"
belongs_to :management_group, class_name: "ClassName"
This is another way around.
It's important to realize there is a convention rails uses for HABTM and has many through. HABTM does not have a model, so it needs to infer the table name which, as others point out, is both plural and alphabetical order.
If you are doing has many through and have a model, the convention is that it wants singular first word, plural second. See examples
User has and belongs to many groups.
HABTM: table name should be groups_users
Has Many Through: table name should be user_groups (flip order is more intuitive)
Model for the latter would be UserGroup. Has many through would specify it as through: :user_groups

Default sort by associated field with reverse_relationship

A miniature may have many contents.
class Miniature < ActiveRecord::Base
has_many :contents, foreign_key: "setmini_id", dependent: :destroy
has_many :minisets, :through => :contents, source: :miniset
has_many :reverse_contents, foreign_key: "miniset_id", class_name: "Content", dependent: :destroy
has_many :setminis, :through => :reverse_contents, source: :set mini
On a miniature's show view I currently list it's contents. What I want to do is add a default sort scope to my Contents model so that it sorts by name.
class Content < ActiveRecord::Base
default_scope { order('name ASC') }
belongs_to :miniset, class_name: "Miniature"
belongs_to :setmini, class_name: "Miniature"
My attempt here fails and complains "No such column name:".
With a normal has_many_through relationship this would work but I'm guessing because I'm using the join table in two directions and declaring class_name: "Miniature" this doesn't work here.
Is there a way I can get this list to default sort by name?
I've tried "content.name" and "setmini.name" to no avail.
You'll need the name of the database table in your order clause (not the model or association name). If you're following Rails' conventions for naming, this would be:
order('contents.name ASC')
After looking at some other answers to similar questions I eventually got it working with the following:
default_scope joins(:setmini).order('miniatures.name ASC')

Newbie here: conflict associations to the same table. has_many:invoices, has_many :invoices, through: user_invoice_viewers

this seems pretty basic stuff here, but actually i'm finding it a bit harsh to define this scenario with Rails...
Perhaps any of you can provide some guidance?
So I have three Tables, Users, Invoices, and User_Invoice_Viewers (these basically map users that have viewer access to an invoice)
Now my models :
User.rb :
has_many :invoices
has_many :user_invoice_viewers
has_many :invoices, through :user_invoice_viewers
Invoice.rb
belongs_to user_invoice_viewers
belongs_to :user
User_Invoice_Viewers.rb
belongs_to :users
belongs_to :invoices
Now this just seems wrong... I repeat has_many :invoices on User model, so i expect conflict when executing : User.invoices ...
What would be the best solution for this? I had thought of putting it all on a user_invoice table, but since i expect to have more owners than viewers, for performance reasons, i decided to build a direct dependency between invoice and its owner...
Thanks
I would consider using the :class_name option on the association, so that the two relationships are named differently. Something like this:
class User < ActiveRecord::Base
has_many :invoices
has_many :user_invoice_viewers
has_many :viewable_invoices, through :user_invoice_viewers, :class_name => "Invoice"
...
end

Resources