Uninitialized constant when using nested attributes on STI subclass - ruby-on-rails

I have a class/model called User which (using Single table inheritance) has a subclass called Student (among others)
Student has a 'has_one' association with 'HomeAddress' which is a subclass of 'Place'
My model set up:
class User < ActiveRecord::Base
end
class Student < User
has_one :home_address, :dependent => :destroy
accepts_nested_attributes_for :home_address
end
class Place < ActiveRecord::Base
end
class HomeAddress < Place
belongs_to :student
end
In my Students controller, I'm trying to create a new Student and associated HomeAddress to pass through to my view:
class StudentsController < ApplicationController
def new
#student = Student.new
#student.build_home_address
end
however, I get an error:
:uninitialized constant Student::HomeAddress
This issue is clearly with the line #student.build_home_address but I don't understand why it's causing this error. I've checked all of naming conventions (i.e. models, controllers, associations) and have other nested attributes relationships working in my app. Perhaps there is an issues with having nested attributes for an STI subclass? (i.e. HomeAddress)
Let me know if you would like to see any other code.
Thanks.

Related

Ruby multiple inheritance needed for rails models

First off, let me say that I know that multiple inheritance is not possible in Ruby, but when I think about it, that's the only way I can see a resolution.
I have 3 separate user classes that all share single-table inheritance. Here they are
class User < ActiveRecord::Base
has_many :notifications
def notify
#code to notify user
end
end
class Student < User
has_many :charges
def charge_user
#code to charge user
end
end
class Teacher < Student
has_many :students
has_many :courses
def create_course
#code to create course
end
end
I want to be able to use each has_many method only once, and the Teacher must inherit all the Student methods
The problem if this is if I run
Teacher.find(1)
I get this error
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'Teacher'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite User.inheritance_column to use another column for that information.
If I change the teacher class to
class Teacher < User
end
It works, however, now the teacher doesn't have all the has_many methods and normal methods that the student has.

Overriding a has_many association getter

A user can have several cars -
User: has_many :cars
Car: belongs_to :user
Every time I call #user.cars it returns the list of cars in default search order.
If I wanted the association sorted on some arbitrary field, I could do
class User < ActiveRecord::Base
has_many :cars, -> { order :num_wheels }
end
But let's say my ordering logic is complex and I want to just override the association getter to implement my own logic
I try something something like -
class User < ActiveRecord::Base
has_many :cars
def cars
# Pretend this is complex logic
cars.order(:num_wheels)
end
end
However that obviously fails because you can't reference the original cars from inside the overridden cars method without it looping infinitely.
Is there a way to reference the "original" getter from inside my overridden getter?
Thanks!
Use super:
class User < ActiveRecord::Base
has_many :cars
def cars
# Pretend this is complex logic
super.order(:num_wheels)
end
end
when you use a macro like has_many, Rails dynamically creates a module(which could be accessed by User.generated_association_methods).In your case, define the accessors and readers(such as "cars" in your case, which could be accessed by User.generated_association_methods.instance_methods). This module becomes the ancestor of your User class, so you can access the reader method(cars) by "super" in your own cars method.
With my understanding I believe what has_many is essentially doing is:
Class User < ActiveRecord::Base
has_many :cars
# is essentially
def cars
Car.where(user_id: self.id)
end
end
So when a user wants to list all the cars it would still be User.cars. When using ActiveRecord the has_many is assuming both the method name of cars and the foreign keys associated.
Try this:
class User < ActiveRecord::Base
has_many :cars
def cars
Car.where(user_id: id).order(:num_wheels)
end
end

Inheriting off an active record model in Ruby on Rails

I was hoping I could inherit off an activerecord model, and use the subclass as I could the parent class. This does not appear to be the case, the AR relationships do not appear to work for the subclass.
class Manager < User
belongs_to :shop
end
class Shop < ActiveRecord::Base
has_many :managers
end
class PremiumShop < Shop
end
and
#premium_shop = manager.shop # Finds the shop.
#premium_shop = manager.premium_shop # Does not find the shop, NilClass:Class error
Is it possible to make this work?
The shop method exists for some instance of the Manager class because of the association you defined through the belongs_to. You have no premium_shop method defined on your Manager model, thus the NilClass error.
If you want to define such an association for the PremiumShop class, you must explicitly specify this.
belongs_to :premium_shop, class_name: "PremiumShop", foreign_key: :shop_id
Depending on your needs, you might also consider researching "rails single table inheritance".

Issue with polymorphic association in a subclass

I have some issues with the polymorphic associations in Rails 3. My model looks like this:
class Address < ActiveRecord::Base
belongs_to :contactable, :polymorphic => true
end
class OrganisationUnit < ActiveRecord::Base
# some other associations
end
# Subclass of OrganisationUnit
class Company < OrganisationUnit
has_one :address, :as => :contactable
end
Now, when I want to get the Address of a Company, Rails generates the following SQL-Query:
SELECT `addresses`.* FROM `addresses` WHERE (`addresses`.contactable_id = 1021 AND `addresses`.contactable_type = 'OrganisationUnit') LIMIT 1
In my opinion it's wrong, because the contactable_type should be "Company".
Is there any way I can fix this or tell rails that OrganisationUnit is just an abstract base class?
The is an expected behavior. When you link a STI table to a polymorphic association, Rails stores the base class name rather than the inherited class names. The STI type conversion happens after the object lookup by id.

How do I model this multi-inheritance relationship w/ Ruby ActiveRecord?

Assuming I have 5 tables. Can ActiveRecord handle this? How would you set it up?
The hierarchy:
Account (Abstract)
CorporateCustomer (Abstract)
PrivateCustomer
PublicCustomer
GovernmentCustomer
Edit: In nhibernate and castle activerecord the method needed to enable this scenario is called "joined-subclasses".
You could try something along the following lines.
class Account < ActiveRecord::Base
belongs_to :corp_or_gov_customer, :polymorphic => true
def account_id
self.id
end
end
class GovernmentCustomer < ActiveRecord::Base
has_one :account, :as => :corp_or_gov_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.account.send( symbol, *args )
end
end
class CorporateCustomer < ActiveRecord::Base
has_one :account, :as => :corp_or_gov_customer, :dependent => :destroy
belongs_to :priv_or_pub_customer, :polymorphic => true
def method_missing( symbol, *args )
self.account.send( symbol, *args )
end
end
class PrivateCustomer < ActiveRecord::Base
has_one :corporate_customer, :as => :priv_or_pub_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.corporate_customer.send( symbol, *args )
end
end
class PublicCustomer < ActiveRecord::Base
has_one :corporate_customer, :as => :priv_or_pub_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.corporate_customer.send( symbol, *args )
end
end
I've not tested this code (or even checked it for syntax). Rather it's intended just to point you in the direction of polymorphic relations.
Overriding method_missing to call nested objects saves writing code like
my_public_customer.corporate_customer.account.some_attribute
instead you can just write
my_public_customer.some_attribute
In response to the comment:
The problem is that concepts like "is a", "has many" and "belongs to" are all implemented by foreign key relationships in the relational model. The concept of inheritance is completely alien to RDB systems. The semantics of those relationships has to be mapped onto the relational model by your chosen ORM technology.
But Rails' ActiveRecord library doesn't implement "is_a" as a relationship between models.
There are several ways to model your class hierarchy in an RDB.
A single table for all accounts but with redundant attributes - this is supported by ActiveRecord simply by adding a "type" column to your table. and then creating your class hierarchy like this:
class Account < ActiveRecord::Base
class GovernmentCustomer < Account
class CorporateCustomer < Account
class PublicCustomer < CorporateCustomer
class PrivateCustomer < CorporateCustomer
Then if you call PrivateCustomer.new the type field will automatically be set to "PrivateCustomer" and when you call Account.find the returned objects will be of the correct class.
This is the approach I would recommend because it's by far the simplest way to do what you want.
One table for each concrete class - As far as I know there is no mapping provided for this in ActiveRecord. The main problem with this method is that to get a list of all accounts you have to join three tables. What is needed is some kind of master index, which leads to the next model.
One table for each class - You can think of tables that represent the abstract classes as a kind of uniform index, or catalogue of objects that are stored in the tables for the concrete classes. By thinking about it this way you are changing the is_a relationship to a has_a relationship e.g. the object has_a index_entry and the index_entry belongs_to the object. This can be mapped by ActiveRecord using polymorphic relationships.
There is a very good discussion of this problem in the book "Agile Web Development with Rails" (starting on page 341 in the 2nd edition)
Search for ActiveRecord Single Table Inheritance feature.
Unfortunately I can't find a more detailed reference online to link to. The most detailed explanation I read was from "The Rails Way" book.
This is one way (the simplest) of doing it:
class Account < ActiveRecord::Base
self.abstract_class = true
has_many :corporate_customers
has_many :government_customers
end
class CorporateCustomer < ActiveRecord::Base
self.abstract_class = true
belongs_to :account
has_many :private_customers
has_many :public_customers
end
class PrivateCustomer < ActiveRecord::Base
belongs_to :corporate_customer
end
class PublicCustomer < ActiveRecord::Base
belongs_to :corporate_customer
end
class GovernmentCustomer < ActiveRecord::Base
belongs_to :account
end
NOTE: Abstract models are the models which cannot have objects ( cannot be instantiated ) and hence they don’t have associated table as well. If you want to have tables, then I fail to understand why it needs to an abstract class.
Assuming most of the data is shared, you only need one table: accounts. This will just work, assuming accounts has a string type column.
class Account < ActiveRecord::Base
self.abstract_class = true
end
class CorporateCustomer < Account
self.abstract_class = true
has_many financial_statements
end
class PrivateCustomer < CorporateCustomer
end
class PublicCustomer < CorporateCustomer
end
class GovernmentCustomer < Account
end
Google for Rails STI, and in particular Rails STI abstract, to get some more useful info.

Resources