has_many children and has_many parents - ruby-on-rails

I'm trying to figure out a complex relation between a Model.
I have a model called "Concept", which has two inheriting types called "Skill" and "Occupation". Basicly this means that each concept represents a category, but a concept can also be a skill or an occupation when going deep enough into the hierychal tree.
I'm solving this hierachy by using STI. So my schema for the Concepts table looks like this:
class CreateConcepts < ActiveRecord::Migration
def self.up
create_table :concepts do |t|
t.string :uri, :null => false, :length => 255
t.string :type, :null => true, :length => 255
t.integer :isco_code, :null => true
t.timestamps
end
end
def self.down
drop_table :concepts
end
end
The type column determins whether the Concept is a real "Concept" or a "Skill"/"Occupation".
The problem now however the following relations:
EDIT:
A Concept can belong to a single parent Concept
An Occupation can belong to a single parent Concept
A Skill can belong to multiple parent Concepts
A skill has no children
An occupation has no children
so basicly you'd have something like this:
> concept1
> concept2 concept3
> concept4 concept5 concept6 concept7 skill1
> occup1 skill2 occup2 skill5
> occup7 skill2 occup3 skill4
> occup4 skill1 occup8
I hope the picture is a bit clear what I'm trying to explain.
Currently I have created the following migration to try to solve the parent-child relation but I'm not sure how to map this with the associations...
class CreateConceptLinks < ActiveRecord::Migration
def self.up
create_table :concept_links do |t|
t.integer :parent_id, :null => false
t.integer :child_id, :null => false
t.timestamps
end
end
def self.down
drop_table :concept_links
end
end
What I want to end up with is the following posssibilities:
concepta.parents => a Concept object
conceptb.children => an array of Conept objects
Occupation.parents => a Concept object
Occupation.children => []
Skill.parents => an array of Concept objects
Skill.children => []
Hope this is even possible...

You can model hierarchical relations in rails. You've got most of the way there with your migrations. Adding the relations below should allow you to do the method calls you'd like:
def Concept < ActiveRecord::Base
has_many :child_links, :class_name => 'ConceptLink', :foreign_key => 'parent_id'
has_many :children, :through => :child_links
has_many :parent_links, :class_name => 'ConceptLink', :foreign_key => 'child_id'
has_many :parents, :through => :parent_links
end
def ConceptLink < ActiveRecord::Base
belongs_to :child, :class_name => "Concept"
belongs_to :parent, :class_name => "Concept"
end
I'd also take a look at this blog posting which does a very good of explaining parent-child mappings in rails.

Related

Trouble with self referential model in Rails

I have a model named User and I want to be able to self reference other users as a Contact. In more detail, I want a uni-directional relationship from users to other users, and I want to be able to reference an owned user of one user as a 'contact'. ALSO, i want to have information associated with the relationship, so I will be adding fields to the usercontact relation (I just edited this sentence in).
I attempted to do this while using the answer to this question as a guide.
Here is the User model:
user.rb
class User < ActiveRecord::Base
attr_accessible(:company, :email, :first_name, :last_name,
:phone_number, :position)
has_many(:user_contacts, :foreign_key => :user_id,
:dependent => :destroy)
has_many(:reverse_user_contacts, :class_name => :UserContact,
:foreign_key => :contact_id, :dependent => :destroy)
has_many :contacts, :through => :user_contacts, :source => :contact
end
I also created the model UserContact as a part of connecting contacts to users:
usercontact.rb
class UserContact < ActiveRecord::Base
belongs_to :user, :class_name => :User
belongs_to :contact, :class_name => :User
end
Here is the create_users.rb migration file i used:
create_users.rb
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :phone_number
t.string :email
t.string :company
t.string :position
t.timestamps
end
end
end
And here is the create_users_contacts.rb migration:
create_users_contacts.rb
class CreateUsersContacts < ActiveRecord::Migration
def up
create_table :users_contacts, :force => true do |t|
t.integer :user_id, :null => false
t.integer :contact_id, :null => false
t.boolean :update, :null => false, :default => false
end
# Ensure that each user can only have a unique contact once
add_index :users_contacts, [:user_id, :contact_id], :unique => true
end
def down
remove_index :users_contacts, :column => [:user_id, :contact_id]
drop_table :users_contacts
end
end
However, for reasons unknown to me, I believe something has gone awry in the linking since on my users index page, I have a column using <td><%= user.contacts.count %></td>, but I get this error from the line when I attempt to load the page:
uninitialized constant User::UserContact
I think the issue may be something to do with the fact that I want to name users associated with another user as contacts, because I cannot find other examples where that is done, and as far as I can tell I am doing everything properly otherwise (similarly to other examples).
The closest similar problem that I found was outlined and solved in this question. The issue was incorrect naming of his connecting model, however I double checked my naming and it does not have that asker's problem.
Any help is appreciated, let me know if any other files or information is necessary to diagnose why this is occurring.
EDIT
After changing usercontact.rb to user_contact.rb, I am now getting this error:
PG::Error: ERROR: relation "user_contacts" does not exist
LINE 1: SELECT COUNT(*) FROM "users" INNER JOIN "user_contacts" ON "...
^
: SELECT COUNT(*) FROM "users" INNER JOIN "user_contacts" ON "users"."id" = "user_contacts"."contact_id" WHERE "user_contacts"."user_id" = 1
EDIT TWO
The issue was that my linking table, users_contacts, was misnamed, and should have been user_contacts! so I fixed it, and now it appears to work!!
You need to rename your usercontact.rb to user_contact.rb
This is naming convention rails autoload works with.

Making ActiveRecord join model attributes available in query results

Given User and Book models, I've created a join model, ViewedBook, that contains additional attributes. Below is the essence of what I've come up with:
create_table "users"
t.string "username"
end
create_table "books"
t.string "title"
t.integer "user_id"
t.date "authored_date"
end
create_table "books_viewings"
t.integer "book_id"
t.integer "user_id"
t.boolean "finished"
t.date "last_viewed_date"
end
class User
belongs_to :book_viewing
has_many :authored_books,
:class_name => "Book",
:source => :book
has_many :book_viewings
has_many :viewed_books :through => :book_viewings
:order => "book_viewings.last_viewed_date DESC"
has_many :finished_books :through => :book_viewings
:conditions => "book_viewings.finished = TRUE",
:order => "book_viewings.last_viewed_date DESC"
end
class Book
belongs_to :user
has_one :author, :class_name => "User"
end
class BookViewing
belongs_to :book
belongs_to :user
end
I think this works for most of the queries I need to create. However, I want each of the Book objects returned by user.viewed_books to include the finished attribute as well. Further, I will have additional queries like Book.best_sellers that I would also like to scope to a given user so that they also include the finished attribute.
From my limited exposure to ActiveRecord, it appears there's probably an elegant way to manage this and generate efficient queries, but I have yet to find an example that clarifies this scenario.
EDIT: to clarify, the other queries I'm looking for will not themselves be restricted to books that have been finished, but I need to have the finished attribute appended to each book if it exists for the given book and scoped user in book_viewings.
See my answer here https://stackoverflow.com/a/8874831/365865
Pretty much, no there isn't a specific way to do this, but you can pass a select option to your has_many association. In your case I'd do this:
has_many :books, :through => :book_viewings, :select => 'books.*, book_viewings.finished as finished'

Combining polymorphic association with many-to-many association... is this setup right?

I have a Genre model, and I want both videos to have many genres and profiles to have many genres. I also want genres to have many videos and genres to have many profiles. I understand the polymorphic and join table stuff, so I'm wondering if my code below will work as I intend it to. Also, I'd appreciate any advice on how to access things in my controller and views.
This is what I envision that the join table should look like (I don't think I need an elaborate :has :through association because all I need in the join table are the associations and nothing else, so the table won't have a model):
genres_videos_profiles:
-----------------------------------------------------
id | genre_id | genre_element_id | genre_element_type
Here's my genre.rb:
has_and_belongs_to_many :genre_element, :polymorphic => true
Here's video.rb:
has_and_belongs_to_many :genres, :as => :genre_element
Here's profile.rb:
has_and_belongs_to_many :genres, :as => :genre_element
Will this work as I intend it to? I'd like some feedback.
As far as I know HABTM associations can´t be polymorphic, I couldn´t find an example like yours in the API documentation. If you want only join tables, your code could look like this:
class Genre
has_and_belongs_to_many :videos
has_and_belongs_to_many :profiles
end
class Video
has_and_belongs_to_many :genres
end
class Profile
has_and_belongs_to_many :genres
end
And access it like Mike already wrote:
#genre.profiles
#profile.genres
#genre.videos
#video.genres
Migrations (for join tables only):
class CreateGenresVideosJoinTable < ActiveRecord::Migration
def self.up
create_table :genres_videos, {:id => false, :force => true} do |t|
t.integer :genre_id
t.integer :video_id
t.timestamps
end
end
def self.down
drop_table :genres_videos
end
end
class CreateGenresProfilesJoinTable < ActiveRecord::Migration
def self.up
create_table :genres_profiles, {:id => false, :force => true} do |t|
t.integer :genre_id
t.integer :profile_id
t.timestamps
end
end
def self.down
drop_table :genres_profiles
end
end
I think that has_and_belongs_to_many can be a bit difficult to follow when it comes to polymorphic (if it even works). So if you want to do the polymorhpic thing, then you can't use any "through" syntax:
class Genre < ActiveRecord::Base
has_many :genres_videos_profiles
end
class GenresVideosProfile
belongs_to :genre
belongs_to :genre_element, :polymorphic => true
scope :videos, where(:genre_element_type => "Video")
scope :profiles, where(:genre_element_type => "Profile")
end
And then you use it like:
# All genre elements
#genre.genres_videos_profiles.each do |gvp|
puts gvp.genre_element.inspect
end
# Only video genre elements
#genre.genres_videos_profiles.videos.each do |gvp|
puts gvp.genre_element.inspect
end
Check out that: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through
For me it was perfect and clean!

eager loading with attributes in join model rails

My app uses a :has_many :through association, and I'm having trouble figuring out how to most efficiently load and display data from both ends of the association and the association itself.
Here are my classes:
class Person < ActiveRecord::Base
has_many :people_ranks
has_many :ranks, :through => :people_ranks
has_many :institutions_people
has_many :institutions, :through => :institutions_people
belongs_to :school
belongs_to :department
end
class Institution < ActiveRecord::Base
has_many :institutions_people
has_many :people, :through => :institutions_people
end
class InstitutionsPerson < ActiveRecord::Base
belongs_to :institution
belongs_to :person
end
and their corresponding models:
create_table :people, :force => true do |t|
t.string :name
t.string :degree
t.integer :year_grad
t.integer :year_hired
end
create_table :institutions, :force => true do |t|
t.string :name
t.integer :ischool
end
create_table :institutions_people, :id => false do |t|
t.integer :institution_id
t.integer :person_id
t.string :rel_type
end
I want to show a person's institution info with something like #person.year_hired, #person.institution.name, and #person.institution.institutions_people.rel_type (where rel_type is either "graduated" or "hired:), but I know that third part won't work. Using the following in the show bit in the person_controller:
#person = Person.find(params[:id], :include => [:school, :department, :institutions_people, :people_ranks, {:institutions_people => :institution}, {:people_ranks => :rank}])
gives me access to #person.institutions and #person.institutions_people, but how do I connect the rel_type attribute from the join to the person-institution relationship? (I'm coming from PHP and now how to build the SQL and loop through it there, but RoR has me stumped.)
I've looked for help under "eager loading" and "associations with :has_many :through", but I get answers about building the associations. My question is really about accessing the association's data after it exists. My app uses static data, and I'm not worried about the update, destroy, or create methods. Thank you for your help!
The way to access the data is through the institutions_people association. So, you would do something like:
me = Person.first
rel = me.institutions_people.first
And then in the view
<%= rel.rel_type %> from <%= rel.institution.name %>
Alternatively, you can give yourself a full list of institutions along with their info:
me = Person.first
And then in the view:
<% for ip in me.institutions_people %>
<%= ip.rel_type %> from <%= ip.institution.name %>
<% end %>

Rails 3 single table inheritence w/ has_many question

I've been trying to setup a Single Table Inheritance model in Rails 3 in which the parent class also contains a has_many relationship. Unfortunately I can't get it to work. Here are three classes as an example:
class Article < ActiveRecord::Base
has_many :paragraphs, :dependent => :destroy, :autosave => true
end
class Paragraph < ActiveRecord::Base
belongs_to :article
end
class SportsArticle < Article
end
And here's the migration that would be used to set this up:
class AddTables < ActiveRecord::Migration
def self.up
create_table :articles do |t|
t.string :type, :null => false # for STI
t.string :title, :null => false
t.timestamps
end
create_table :paragraphs do |t|
t.references :article, :null => false
t.timestamps
end
end
def self.down
drop_table :articles
drop_table :paragraphs
end
end
When I set it up this way and I try to create a new SportsArticle, say by doing the following:
SportsArticle.create(:title => "Go Giants")
I always get the following error:
"TypeError: can't convert String into Integer"
I have no idea how to fix this issue and have tried finding a solution online to no avail. Does anybody who has experience with STI models see anything wrong? Here's the link to the documentation on the create method if it will help in diagnosing the problem:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-c-create
Try renaming :type to something else, like :article_type
eg:
t.string :article_type, :null => false # for STI
The error was being caused due to a naming collision. I was using a name for one of my models called "attributes" which was causing the problem. The hint that eventually diagnosed the problem came from the Rails Association Documentation.

Resources