I have a Users model which has many Projects. But each Project are of different types. A WebApplication, DesktopApplication and so on. All these different types have their own specific fields and yet they share common fields which will be stored in the Projects table.
I have thought of this solution having multiple has_one to each of the Project types in the project model. Is this the way to go?
Your best bet is probably one User to many Projects, then have an "extended info" that's polymorphically associated. I think that an example would describe better than that sentence.
class User < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :user
has_one :project_type, :as => :type
end
class ProjectType < ActiveRecord::Base
belongs_to :type, :polymorphic => true
end
class WebApplication < ProjectType
# fields here
end
class DesktopApplication < ProjectType
# fields here
end
#project.type = WebApplication.new
#otherproject.type = DesktopApplication.new
Unfortunately I can't test this to guarantee that it works, but I think I got everything correct :)
Related
So I am making an app that reviews books, articles and the like.
I have created the backbone of the app by creating models, views, controllers etc for Piece(the book or article), Section(self explanatory), Subsection, and Subsubsection.
I want to add a new model into the mix, a "Links" model (which will just be a link to another source or website). My issue is that I don't know how to make ALL of my previously stated models have "Links". I want each of The above models to have access and CRUD capabilities to their "Links", but so far all i have read about is has_many or has_and_belongs_to_many.
As far as I understand, those kinds of relations only relate ONE model to ONE other model, even if Piece might have many Sections, it only relates these two models.
I guess the Links model would have to have an obligatory piece_id, but then optional id's such as: section_id, subsection_id depending on where the link was. So if in Chapter 3 of my first book i want to add a link, it would have an obligatory piece_id=1 and then a section_id=3, but then no subsection_id or subsubsection_id.
So how do I go about creating a model such that it belongs to several other models? Or is this even possible?
https://github.com/kingdavidek/StuddyBuddy
Ok, it sounds like essentially you want a polymorphic association
class Link
belongs_to :linkable, polymorphic: true
end
class Piece
has_many :links, as: :linkable
end
Link would need linkable_id integer column and linkable_type string column. You can then use it in the same way as an ordinary has_many to belongs_to association
if i wanted to create a new Link in a Subsection, it would belong to
Subsection, but also to Section and Piece because of the nested
relationship
This bit rails can't help with, you'd need to write your own method to find all the links in the chain of items.
This is a pretty good use case for polymorphic associations. For simplicity lets start out with a one to many relationship:
class Link < ActiveRecord::Base
belongs_to :linkable, polymorphic: true
end
class Piece < ActiveRecord::Base
has_many :links, as: :linkable
end
class Section < ActiveRecord::Base
has_many :links, as: :linkable
end
Here the links table will have linkable_id (int) and linkable_type (string) columns. One important thing to take note of here is that linkable_id is not a true foreign key from the RBDMS point of view. Rather ActiveRecord resolves which table the relation points to when it loads the relation.
If we want to cut the duplication we can create a module which contains the desired behavior. Using ActiveSupport::Concern cuts a lot of the boilerplate code involved in creating such a module.
class Link < ActiveRecord::Base
belongs_to :linkable, polymorphic: true
end
# app/models/concerns/linkable.rb
module Linkable
extend ActiveSupport::Concern
included do
has_many :links, as: :linkable
end
end
class Piece < ActiveRecord::Base
include Linkable
end
class Section < ActiveRecord::Base
include Linkable
end
So how would we make a polymorpic many to many relation?
class Link < ActiveRecord::Base
has_many :linkings
end
# this is the join model which contains the associations between
# Links and the "linkable" models
class Linking < ActiveRecord::Base
belongs_to :link
belongs_to :linkable, polymorphic: true
end
# app/models/concerns/linkable.rb
module Linkable
extend ActiveSupport::Concern
included do
has_many :links, through: :linkings, as: :linkable
end
end
class Piece < ActiveRecord::Base
include Linkable
end
class Section < ActiveRecord::Base
include Linkable
end
On a side note - a better way to build a hierarchy between sections would be to use a single Section model and give it a self joining relationship.
class Section < ActiveRecord::Base
belongs_to :parent, class_name: 'Section'
has_many :children, class_name: 'Section',
foreign_key: 'parent_id'
end
I'm using single table inheritance in my application and running into problems building inherited users from an ancestor. For instance, with the following setup:
class School < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
attr_accessible :type #etc...
belongs_to :school
end
Class Instructor < User
attr_accessible :terms_of_service
validates :terms_of_service, :acceptance => true
end
Class Student < User
end
How can I build either a instructor or student record from an instance of School? Attempting something like School.first.instructors.build(....) gives me a new User instance only and I won't have access to instructor specific fields such as terms_of_service causing errors later down the rode when generating instructor-specific forms, building from console will give me an mass-assignment error (as it's trying to create a User record rather than an Instructor record as specified). I gave the example of School, but there are a few other associations that I would like to inherit from the User table so I don't have to repeat code or fields in the database. Am I having this problem because associations can not be shared in an STI setup?
You should specify instructors explicitly
class School < ActiveRecord::Base
has_many :users
has_many :instructors,:class_name => 'Instructor', :foreign_key => 'user_id'
end
And what else:
class School < ActiveRecord::Base
has_many :users
has_many :instructors
end
class Instructor < User
attr_accessible :terms_of_service # let it be at the first place. :)
validates :terms_of_service, :acceptance => true
end
OK it seems part of the problem stemmed from having the old users association inside of my School model. Removing that and adding the associations for students and instructors individually worked.
Updated School.rb:
class School < ActiveRecord::Base
#removed:
#has_many :users this line was causing problems
#added
has_many :instructors
has_many :students
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.
I have a weird case in my rails development that I'm not able to manage properly.
Basically, I have three objects: Domain, Project and Person ; a domain is a group of persons and projects. Domains can have several projects and projects can have several people however a project can only be in one domain and people can only work for projects in one domain.
I have represented it as following:
class Domain < ActiveRecord::Base
has_many :projects
class Project < ActiveRecord::Base
belongs_to :domain
has_and_belongs_to_many :persons
class Person < ActiveRecord::Base
belongs_to :domain
has_and_belongs_to_many :projects
I don't know how to validate that all the projects added to a person belongs to the same domain. I have created a method for validating persons however it is still possible to add projects in other domains, the person saved in database will just not be valid.
Do you see a clean solution to this problem?
So, basically, you want to validate that a person takes projects only from one domain. I suppose this domain should be defined, meaning a person should have a domain_id column.
You also have a many-to-many association, and, since the association needs some validations, you should have also a join model (instead of just a table without a model). I called it Work. So, I have this:
class Domain < ActiveRecord::Base
has_many :projects
end
class Project < ActiveRecord::Base
belongs_to :domain
has_many :works
has_many :persons, :through => :works
end
class Work < ActiveRecord::Base
belongs_to :project
belongs_to :person
end
class Person < ActiveRecord::Base
has_many :works
has_many :projects, :through => :works
end
Now, to the Work model you just add
validate :projects_belong_to_apropriate_domains
def projects_belong_to_apropriate_domains
if person.domain_id != project.domain.id
errors[:base] << "A person may only take a project which belongs to his domain."
end
end
This worked for me. Is this what you wanted?
You could setup a custom validation method for Person (taken from the rails guides)
validates :check_project_domain
def check_project_domain
projects.all.each do |p|
next if domains.exists?(p.domain.id)
errors.add :project_domain "#{p} is not a member of allowed domains"
end
end
I'm not to sure if you can call exists on a association, if not then you could replace it with something like:
domains.all.collect { |d| d.id }.include?(p.domain.id)
or even:
domains.where(:id => p.domain.id).count > 0
I have 2 models in different namespace.
class Admin::Membership < ActiveRecord::Base
has_many :authorization_roles
end
class AuthorizationRole < ActiveRecord::Base
belongs_to :membership
end
The Membership model is in different folder with AuthorizationRole model (I don't know what is called)
When run Admin::Membership.find(:all), the data from AuthorizationRole model is not included. I've create membership_id field on authorization_roles table, but I still can't get both models related. Is something wrong in this code? Sorry if I'm missing something basic here.
Try this
class Admin::Membership < ActiveRecord::Base
has_many :authorization_roles, :class_name => '::AuthorizationRole'
end
class AuthorizationRole < ActiveRecord::Base
belongs_to :membership, :class_name => 'Admin::Membership'
end
I've never used namespaced models and I don't think you need to... but maybe you'll have to specify the class name in AuthorizationRole, something like:
belongs_to :membership, :class_name => 'Admin::Membership'
UPDATE:
Assuming you have:
class Membership < ActiveRecord::Base
has_many :authorization_roles
end
class AuthorizationRole < ActiveRecord::Base
belongs_to :membership
end
You have added an integer column called membership_id to authorization_roles and you've run the migrations. Now you should be able to create authorization_roles like this #membership.authorization_roles.create( ... ) and fetch them #membership.authorization_roles
Check to see if you are setting the table name prefix. The Rails model generator adds a file like this for namespaced models:
# /app/models/admin.rb
module Admin
def self.table_name_prefix
'admin_'
end
end
Note: this is Rails version 3.0.1 -- not sure about earlier versions.