Validates association of an object - ruby-on-rails

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

Related

ActiveRecord won't build the right class using STI

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

Model association between Company, Employee, and department

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.

How to create multiple relations between the same models

I have a User model
class User < ActiveRecord::Base
has_many :projects
end
and I have a Project model
class Project < ActiveRecord::Base
belongs_to :user
end
Obviously right now each project is owned by a user and there can only be one user per project. I now want to make my models represent another relation between the two models. I want a User to be able to follow multiple Projects, no matter who owns the Project. I know that I am going to have to use a has_many :through and create a join, but I cant wrap my head around how to change the model to keep my current relationship and add the new relationship.
Well, in that case, in your show/index action display all the projects (Project.all) in your project table. This way all users have access to all the projects. Now, in your edit action, use user.projects.all to display projects of that particular user. That should solve your problem, I don't see the need of any further association here.
Update:
This should suffice:
class Project < ActiveRecord::Base
belongs_to :user
class User < ActiveRecord::Base
has_many :projects_followed, :through => :projects
user has_many :projects_owned, :through => :projects
If you don't wish to create two more relations, create just one:class ProjectsSubscribed
belongs_to :project with three fields: project_id, is_owned, is_followed
Try following relation.
class User < ActiveRecord::Base
has_many :followers
has_many :projects, :through => :followers
end
class Follower < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
class Project < ActiveRecord::Base
has_many :followers
has_many :users, :through => :followers
end
Note it:
You can use has_many :through relationship if you need to work with the relationship model as an independent entity. If you don’t need to do anything with the relationship model, it may be simpler to set up a has_and_belongs_to_many relationship.
Hope it is helpful.

Rails Associations Help

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 :)

rails has_many through with independent through table

I have a User model, Person model and Company model.
a User has many companies through Person and vice versa.
But i would like to be able to populate People and Companies that are not tied to Users that can be tied later.
class User < ActiveRecord::Base
attr_accessible :name
has_many :people
has_many :companies, :through => :people
end
class Person < ActiveRecord::Base
attr_accessible :user_id, :company_id
belongs_to :users
belongs_to :companies
end
class Company < ActiveRecord::Base
attr_accessible :name
has_many :people
has_many :users, :through => :person
end
now in the console i want to be doing the following
User.find(1).companies
then it should find me the companies in which user(1) is a person of interest.
Have I got this wrong, is there a small change that I should be making.
Your Person model can't directly "belong_to" more than one, your belongs_to :users and belongs_to :companies associations won't work that way. Companies-to-people need to be connected through another join table that describes the relationship between them, for example Employment which points to one instance of each model:
class Person < ActiveRecord::Base
has_many :employments
has_many :companies, :through => :employments
end
class Employment < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :employments
has_many :people, :through => :employments
end
You can then use the :through option to associate the many companies/people on the other side of that employment relationship in the middle.
Similarly, if a Person can be owned by more than one User then you will need a join model between those two entities as well.
Just as a followup, in a has_many :through relationship, there is nothing that says you cannot use your join table (in your case, Person) independently. By nature of the relationship, you are joining through a completely separate ActiveRecord model, which is what most notably distinguishes it from the has_and_belongs_to_many relationship.
As Brad pointed out in his comment, you need to pluralize 'person' to 'people' in your relationship. Other than that, it looks like you set it up correctly. Exposing :user_id and :company_id with attr_accessible will enable you to mass-assign these values later from a postback, but often times you want to shy away from doing so with role-based associations, as you may not want to leave them exposed to potential HTTP Post attacks.
Remember, in your controller you can always do something like this with or without attr_accessible:
#person = Person.new
#person.user = User.find(...)
#person.company = Company.find(...)
#person.save
Hope that helps.

Resources