I'm currently developing an application using Rails 3.2 and have run into a bit of a problem. I know this has been asked hundreds of times before but I couldn't find an answer that solved it. Here is a similar ER: http://i.stack.imgur.com/x5V0G.png
Fairly obvious what i'm trying to do. I'm hoping for the association to read like the following:
Supplier.first.theatres.first.events.first.orders
TourEvent.first.orders
Tour.first.orders
Now it would be nice to be able to define my models like so:
class Event < ActiveRecord::Base
has_many :orders
belongs_to :eventable, polymorphic: true
# id, eventable_id, eventable_type, title, date, price
end
class TourEvent < Event
belongs_to :tour
# id, tour_id, rendezvous, guide_name
end
class Tour < ActiveRecord::Base
has_many :events, class_name: 'TourEvent'
# id, name, venue, duration
end
But I understand that's reserved for "STI" rather than "MTI". Any ideas how to get my solution working without the need for complicated mixins or plugins? Or is it just not possible?
I think you can make something like this:
suppliers
has_many :events
events
belongs_to :suppliers
belongs_to :institutions
has_many :orders
# id, supplier_id, institution_id, ...
institutions
# id, type, title, ...
types: theatre, club, tour
orders
belongs_to :events
# id, event_id
Then, you can access to event orders:
Supplier.first.events.first.orders
Related
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
I would like to achieve something as follows where PersonSubject has many topics, but the choices of these topics are limited to the the selection of topics through another model (ie: through the associated subject):
class Topic < ApplicationRecord
belongs_to :subject
end
class Subject < ApplicationRecord
has_many :topics
end
class PersonSubject < ApplicationRecord
belongs_to :person
belongs_to :subject
has_many :topics # where the choices are limited to the subject.skills
end
I would then like if any person_subject.subject.topics are deleted (or association removed), it would automatically update the person_subject.topics to no longer "point" to the Topic(s) that were deleted.
Is this possible?
You can use a lambda to put arbitrary filters on an association. See What is the equivalent of the has_many 'conditions' option in Rails 4?
has_many :topics, -> { where(skill: subject.skills) }
I don't know that this is exact code will work without seeing your schema (what is the data type of subject.skills, and how do you join this with topic?). But hopefully this gets you on the right track
edit
in response to your comment, I think
has_many :topics, through: :skills
would work
I have the following models:
class User < ActiveRecord::Base
has_many :forum_users
has_many :forums through: :forum_users
end
class Forum < ActiveRecord::Base
has_many :forum_users
has_many :users through: :forum_users
end
class ForumUser < ActiveRecord::Base
belongs_to :forums
belongs_to :users
end
ForumUser has an additional "permissions" field to designate what the user can and cannot do within a particular forum.
The problem is that the "permissions" field on ForumUser is much more difficult to access than fields on Rails models normally are. In an ideal world, I would be able to make requests like:
user = forum.users.first
user.permissions
But once it's assigned, the User doesn't even remember the Forum it was attained through in the first place. It seems like there has to be a simple and straightforward Rails way of doing this. If so, what is it?
I have a Post model that has_many :authors and has_many :tags through :tag_posts, an Author model that has_many :posts and has_many :tags through :posts, and a Tag model that has_many :posts through :tag_posts.
Posts have to pass moderation (stored as an enum Post.status) before they're visible on the site. Moderators can see unmoderated posts by looking for ones with a different Post.status, but regular users should never be able to see them.
Is there any way I can filter all these associations to make sure they only return the posts that moderators have approved?
For example: if I call Author#tags now, that returns all the tags on all the posts that author has written, but I only want it to return the tags on the approved posts that the author has written. Say the author has two posts, the first is approved and is tagged 'octopus', and the second hasn't been approved yet and is tagged 'squid': calling #tags on that author should only return the octopus tag, not the squid tag.
I have a Post model that has_many :authors and has_many :tags through :tag_posts, an Author model that has_many :posts and has_many :tags through :posts, and a Tag model that has_many :posts through :tag_posts.
First thing's first, let's tidy up those relations. I believe this is what you really want:
class Post < ActiveRecord::Base
belongs_to :author # NOT has_many
has_and_belongs_to_many :tags # use an implicit join table
end
class Author < ActiveRecord::Base
has_many :posts
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts # use an implicit join table
end
Is there any way I can filter all these associations to make sure they only return the posts that moderators have approved?
Yes. Without going into to much detail, the key technique is to define a scope on the Post model; something like this:
class Post < ActiveRecord::Base
scope :approved -> { where(status: Post.statuses['approved']) }
end
You can then show only the approved posts via Post.approved; or only the approved posts for a certain user via Post.where(user: foo).approved.
In order the display a list of tags, you could then, for example, just do:
Post.where(user: foo).approved.map(&:tags).uniq
This will only give you an Array, not an ActiveRecord::Collection. But that's probably good enough, for your purposes.
You can scope has_many itself.
Eg.
class Author
has_many :moderated_posts, -> { moderated }, class_name: 'Post'
end
class Post
scope :moderated, -> { where(status: approved_statuses) }
end
I am working in Ruby on Rails 3. And trying to map out three models which mimic the data of a Company its employees and their respective departments.
In arrived at the following solution:
class Company < ActiveRecord::Base
has_many :departments
has_many :employees, through => :departments
end
class Department < ActiveRecord::Base
belongs_to :company
has_many :employees
has_one :department_description
end
class DepartmentDescription < ActiveRecord::Base
belongs_to :department
end
class Employee < ActiveRecord::Base
belongs_to :department
end
Is this the 'correct' way to associate these models?
I think your last response may explain why you are struggling to find a correct way to associate these models.
It seems that you see your Department merely as a join_table, and that may be due to the fact that you don't fully understand the has_many => :through construction and that it actually allows your Department to be a proper model with many attributes and methods in it, hence also a 'description' attribute.
To create a separate DepartmentDescription model is actually a waste of resource. Chad Fowler has a few good examples for :has_many => through and nested resources in his Rails Recipes... so check that out.