ActiveRecord won't build the right class using STI - ruby-on-rails

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

Related

RailsActive Record and Nested Resources

I would like for click action on a button to result in the addition of a resource to a join table. There are several ways to do this in the console but I can't for the life of me figure out how to implement this outside the console.
Here is an example that mimics my current model:
class Student < ActiveRecord::Base
has_many :reports
has_many :schools, through: :reports
end
class School < ActiveRecord::Base
has_many :reports
has_many :students, through: :reports
accepts_nested_attributes_for :reports
end
class Report < ActiveRecord::Base
belongs_to :student
belongs_to :school
accepts_nested_attributes_for :student
accepts_nested_attributes_for :school
validates :student_id, presence: true
validates :school_id, presence: true
end
This method returns all the report cards that belong to a student (student already exists in
my database):
#student.reports
This method returns all the schools that a student has attended:
#student.schools
I can add/associate an existing school to a student by doing this:
#school = School.find(params[:id])
if student.present?
#student = Student.find(params[:id])
#student.schools << #school
Please note that the association of a single report to many students is intentional. My question now is how do I enable a student to add a school to their report simply by clicking on a particualr school? My reports table (which basically is a join table) should be automatically updated as soon as this click_action takes place/happens (that is a new row that associates that particular student_id with that particular school id should be created).
Been trying to figure it out but not making progress for some reason. Thank you in advance!
Well, to start with, in your view you should have a javascript/DOM event for each school:
onclick(window.location.href = "<%= path_to_add_school_to_students(student,school) %>")
So there you have your one click.
In your controller
student=Student.find(params[:student])
if student.schools.find(params[:school]).nil? # if school not already assigned
student.reports.create(:school_id => params[:school])
end

Rails admin hide belongs_to field in has_many nested form

I have two Models
class Entity < ActiveRecord::Base
# Associations
has_many :contacts
accepts_nested_attributes_for :contacts, :allow_destroy => true
end
class Contact < ActiveRecord::Base
# Associations
belongs_to :entity
end
Now in rails admin I am getting below options.
Add new Contact Form
Add new Entity Form
I need to hide Entity field in contact form , while adding new entity.
Any help will be useful.
You can automatically hide the fields using inverse_of like this
class Entity < ActiveRecord::Base
# Associations
has_many :contacts, inverse_of: :entity
accepts_nested_attributes_for :contacts, allow_destroy: true
end
class Contact < ActiveRecord::Base
# Associations
belongs_to :entity, inverse_of: :contacts
end
If you set the :inverse_of option on your relations, RailsAdmin will
automatically populate the inverse relationship in the modal creation
window. (link next to :belongs_to and :has_many multi-select widgets)
Source: https://github.com/sferik/rails_admin/wiki/Associations-basics
Let me know how it went
For the sake of completness and because i had this problem too and solved it, if you want to you can configure a model when it is used inside a nested form just like you do with edit, update, create and nested
class Contact < ActiveRecord::Base
# Associations
belongs_to :entity
rails_admin do
nested do
configure :entity do
hide
end
end
end
end
Visit the official wiki for more info

rails model assignment with has_many :through

I just can't figure out how to create a relation with a join table. I've read all the posts about them, but the main error seems to be that in the join table to models should be singular, which I have. I just can seem to create the models correctly and assign them. I have projects with datasets, and projects can have multiple datasets, while a dataset can belong to multiple projects. A dataset can be active or not, which is why I need the has_many through instead of the has_many_and_belongs_to setup.
My model definitions are:
class Project < ActiveRecord::Base
attr_accessible :name, :user_id
belongs_to :user
has_many :activedatasets
has_many :datasets, :through => :activedatasets
end
class DataSet < ActiveRecord::Base
attr_accessible :name, :project_id, :filename, :tempfilename
has_many :activedatasets
has_many :projects, :through => :activedatasets
end
class ActiveDataSet < ActiveRecord::Base
attr_accessible :active, :data_set_id, :project_id
belongs_to :project
belongs_to :dataset
end
When I create a new dataset I've got the project_id in the params, so I'm trying to setup the relationship like below:
class DataSetsController < ApplicationController
def new
#dataset = DataSet.new
#dataset.activedatasets.project_id = params[:project_id]
end
end
The error I'm getting seems famous:
NameError in DataSetsController#new
uninitialized constant DataSet::Activedataset
Can anybody point me in the right direction please?
Thanks for you attention.
You need to use:
has_many :active_data_sets
has_many :data_sets, :through => :active_data_sets
And in the DataSet model:
has_many :active_data_sets
has_many :projects, :through => :active_data_sets
Basically, rails expects you to use underscores to separate words in association names, and converts them to CamelCase. So active_data_sets becomes ActiveDataSet. Rails then uses this to work out which model class the association is with.
You also need to change your controller to this:
class DataSetsController < ApplicationController
def new
#dataset = DataSet.new
#dataset.active_data_sets.build(:project_id => params[:project_id])
end
end
Otherwise you'll get an error because you tried to set the project_id of the active_data_sets collection rather than creating a new ActiveDataSet.

Rails make a model require another model

If I have a has_and_belongs_to_many relationship between two models, let's say Users and Accounts, can I require that a User have at least one Account, and how?
Also, using the has_and_belongs_to_many relationship, is it possible for an Account not to have a User?
What I need is a relationship where Accounts can live on their own, and belong to Billers, but they can also belong to Users if a User signed up with one. Is this possible, and how?
I personally would drop the the HABTM. Instead I would use has_many :though=>
You would need to create two new models, account_users, and account_billers. You likely already have join tables for the HABTM, but this will expose them as models so they will need ID fields.
So you would end up with something like the following:
class Account < ActiveRecord::Base
has_many :account_billers
has_many :account_users
has_many :billers, :through=> :account_billers
has_many :users, :through=> :account_users
end
class User < ActiveRecord::Base
has_many :account_users
has_many :accounts, :through=>:account_users
validates :accounts, :length => { :minimum => 1}
end
class Biller < ActiveRecord::Base
has_many :account_billers
has_many :accounts, :through=>:account_billers
validates :accounts, :length => { :minimum => 1}
end
class AccountUser < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class AccountBiller < ActiveRecord::Base
belongs_to :biller
belongs_to :account
end
To validate presence of at least one association, you might want to use a custom validation method, something like
class User < ActiveRecord::Base
has_and_belongs_to_many :accounts
validate :require_at_least_one_account
private
def require_at_least_one_account
errors.add(:accounts, "must amount to at least one") if accounts.size < 1
end
end
(Although this brings a question of how an account is shared between users)
For your second question, looks like polymorphic associations are what you're looking for, but you can't do this straight with a HABTM relationship, you'll have to change it to a has_many :through and introduce a join model.

Validates association of an object

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

Resources