Chapter 10: Multiple Table Inheritance

Back to Table of Contents.

Multiple Table Inheritance

We’ve spent a lot of time on the idea of tweaking your data model until it’s rock solid, impervious to application-layer bugs or a meddling Martha at the psql prompt. By now you should feel like you’re ready to practice referential integrity jujitsu or constraint kung fu. It may come as a surprise, then, to learn that there is a feature of ActiveRecord—polymorphic associations—that depends on breaking the referential integrity we’ve worked so hard to ensure.

Before you get your knickers in a knot, remember that ActiveRecord was primarily developed against MySQL, at a time when referential integrity was a “feature” that MySQL did not support. So it’s understandable that some “features” crept into ActiveRecord and Rails that are not really features at all, but disasters waiting to happen.

In this chapter, we’ll examine polymorphic associations, understand the problem they were intended to solve, and come up with a better solution that allows us to preserve referential integrity. We’ll continue with our practice of pairing a powerful new pattern at the application layer with getting the most we can for free out of the data layer, allowing the two layers to work together side by side.

The Problem

Polymorphic association allows you to define a relationship between two tables without knowing ahead of time what one of the tables in the relationship is. This allows you to define “xor” relationships: “A has one of B or C, but not both.” Usually it’s assumed that B and C are of similar logical types, but just happen to be stored in different tables.

As an example, consider an object model for an employee who must choose between health plans of different styles, HMOs versus PPOs, as shown in Figure 10-1.

er_1001Figure 10-1. Object model for an employee associated with one of a set of different health plans

We see that in our object model, we want to differentiate between pan types. HMOs and PPOs definitely behave differently. We could still represent this at the data layer with a single table, using Rails’ built-in single-table inheritance mechanism, but recognizing that HMOs are evil, the architect of this model chose to keep them separate.

Polymorphic associations to the rescue! Assuming we keep our health plan tables separate, one for HMOs and one for PPOs, we can define our Employee class like so:

def Employee < ActiveRecord::Base
  belongs_to :health_plan, :polymorphic => true
end

To specify an association is polymorphic, you say :polymorphic => true in an association definition. To support this, the employee table must look like this:

create table employees (
  id integer not null
    default nextval('employees_id_seq'),
  name varchar(64) not null,
  health_plan_id integer not null,
  health_plan_type varchar(32) not null
);

This trick is implemented in the data layer with two columns for the association. The first is the foreign key reference, {foreign_table}_id. The second specifies the table name of the foreign object: {foreign_table}_type.

This seems great—a veritable panacea to the problem of multiple table inheritance; unfortunately, the Rails approach to polymorphic associations is at odds with a design that hopes to enforce referential integrity. Because it’s not known what table foreign_table_id refers to, it’s not possible to add a foreign key constraint enforcing the relationship. This opens the design back up to the possibility of orphaned associations and generally invalid data. In fact, this construct is completely unconstrained at the application layer. You can assign any object into the health_plan attribute, not just HMOPlan or PPOPlan objects. What seemed like an elegant solution is suddenly a black hole for bugs.

In this chapter we’ll learn a better way to handle this sort of relationship. We’ll start with a discussion of polymorphism in general. Understanding this important computer science principle will help us avoid abusing it. Next we’ll look at two mechanisms for inheritance: single table inheritance (STI) and multiple table inheritance (MTI). Rails supports STI out of the box. When you use it, you don’t need a “polymorphic association.” Rails doesn’t, however, support true MTI out of the box, and this is where polymorphic associations come into play; they attempt to solve that problem, but not very well. We’ll see how to make MTI work in Rails, then learn how to make polymorphic associations work correctly in both the data and application layers in the section “XOR on Columns,” later in this chapter.

With this technique, the relationship is just as easy to manage from the Rails application layer as polymorphic associations. We’ll also have the added benefit that the database is put to work on our behalf to ensure the integrity of our data.

What Is Polymorphism?

Polymorphism is the property in a programming language that allows objects of different types to be substituted for one another in program flow without needing to know ahead of time what the object’s type is.

Many people would take issue with that definition as overly simplistic. Indeed, most discussions about polymorphism are much more intricate because they become caught up in the syntax of a particular language and how you achieve polymorphism, as if it’s an epic battle. This leads to discussion of the places where you can have polymorphism, followed by what hoops you must jump through to get polymorphism. In Ruby, polymorphism is everywhere, so it’s much simpler.

Yes, polymorphism applies to methods as well as objects. For example, we could define a method plus, which just applies + to two parameters:

def plus(a, b)
  a + b
end

This method doesn’t care what the types of a and b are. Method signatures are completely untyped. We also see polymorphism in the + operator itself. As long as it has meaning for the parameters, the method will work:

>> plus(1, 2)
=> 3
>> plus("hel", "lo")
=> "hello"
>> plus([1], [2])
=> [1, 2]

We can also demonstrate polymorphism at the object level; through inheritance, subclasses can take on specialized behavior not found in the base class:

class Animal
  def noise
    raise "Noise not defined for #{self.class}"
  end
end

class Dog < Animal
  def noise
    "Woof!"
  end
end

class Cat < Animal
  def noise
    "Meow!"
  end
end

We can now iterate over a list of animals printing out their noises:

>> [Cat.new, Dog.new].each{|a| puts a.noise}
Meow!
Woof!

We called the noise method on each object, and the right noise was made for each animal, because the method was overridden in each class definition. In many languages—as well as in our example above—the language feature we used to implement polymorphism was inheritance. However, in Ruby, we don’t need to use inheritance to get this behavior. We could have left the Animal class out altogether, as below, and the output of our puts loop would be identical:

class Dog
  def noise
    "Woof!"
  end
end

class Cat
  def noise
    "Meow!"
  end
end

In Ruby, inheritance is a mechanism for sharing common behavior, but it’s not a prerequisite to achieve polymorphism. When we iterate over a list, there is no requirement that the list members be of the same type or inherit from a common ancestor (truth be told, all objects in Ruby do inherit from Object). In many other languages, class inheritance (or in Java, the use of interfaces) is how you achieve polymorphism with objects. Not so in Ruby, due to duck typing. In Ruby, you never specify the expected types of inputs to methods or their return values, nor do you specify the type of objects composing a list, hash, or other structure; as long as an object has the properties a caller expects it to have, everything just works. The distinction between polymorphism for methods as opposed to polymorphism for objects is blurred in Ruby. In most languages, the need for a distinction is borne out of implementation syntax. In Ruby, these syntactical considerations simply don’t exist.

Inheritance and Persistence

Aside from utility functions, most polymorphism in web programming is related to inheritance, be it strict class inheritance, or implementation of interfaces through module includes.

The consideration, then, is how to store these hierarchies of objects in the database. Should the data be stored in a single table or in multiple tables? And if the latter, how do we do this in the context of Rails, using ActiveRecord? In this chapter we’ll expand upon the payment data model we left off with in Chapter 5, reworking it to be more complete from the perspective of our real-world application. In doing so, we’ll encounter a case of single table inheritance (STI), which Rails supports by default. We’ll also encounter an example of multiple table inheritance (MTI).

Since our application is about to get rather complex, we begin by developing a logical model for the order payment system before we jump to the physical model. The logical model is shown in Figure 10-2. We still have an order, which contains multiple tickets. However, we now split off the payment into its own class. We have three distinct types of payments: a credit card payment, as we had previously, a Paypal payment, and also a free promotional payment, which might be granted for entering a valid promotional code. The CreditCard class is a super class of three accepted card types: AmEx, Visa, and MasterCard. All credit card payments need address information, so the CreditCard class also implements the Address interface via a module include.

er_1002Figure 10-2. Logical model for the order payment system

We have two inheritance hierarchies: one descending from CreditCardPayment, and one descending from Payment. Logically there is no practical difference. The question to ask when determining what type of physical inheritance to use—single- or multiple-table—we consider whether subclasses actually share any physical data. If all or even most of the data is shared from one subclass to the next, then single table inheritance is an appropriate choice. However, if the classes do not have much data in common, then multiple table inheritance (or even no inheritance at the data layer) is the right choice.

It’s tempting to use single table inheritance for everything because it’s built-in, but this is a big mistake. When you use single table inheritance in cases where there is little data overlap, your data model becomes confusing; many of the columns are not intended to be filled except under certain circumstances—when the row is of the appropriate subtype. Similarly, the application layer becomes polluted with getter and setter methods for each physical column in the table; when viewed from the logical model’s perspective, these columns and their getters and setters aren’t intended to be there. When you use single table inheritance, the physical model of the data layer bleeds through to the logical model of the application.

STI also has another drawback—class names get saved to the database to identify the type of each row, linking code and data in a way that can have unexpected consequences as your application matures. One constraint you impose upon yourself by using STI is that your class names become more or less set in stone. If you decide to change them, you must update all the records that reference the original classes. On a production database with millions of records and active users, making such change is practically impossible. It could take hours, and in the meantime, either your site slows to a crawl, or it simply doesn’t work, because STI relationships can’t be figured out.

Single Table Inheritance (STI)

We have recast our credit card model from Chapter 4. There we left off with credit cards implementing a sort of strategy pattern; credit card objects were constants whose methods could be applied to orders to get a job done, such as processing a payment. This time we’ll take a different approach. Here, we’ve split off the payment information from the Order class. Each CreditCardPayment object will contain the data necessary to process a transaction: the credit card number, expiration information, and any address data necessary to authorize the transaction.

The object itself can take care of the processing, operating on its own local data. But there won’t ever be any CreditCardPayment objects; the objects will be instances of one of the subclasses (MasterCardPayment, VisaPayment, and AmexPayment), which will define the particular behavior for processing transactions of that type. Because the types of information supplied by users to process a credit card payment is the same regardless of the payment type, this is a perfect case for single table inheritance. The physical single table that holds credit card payments for AmEx, Visa, and MasterCard is shown in Figure 10-3. The type column changes depending on which card type was chosen, specifying which class should be used.

er_1003Figure 10-3. Logical and physical models for CreditCardPayment and its subclasses; physical model uses STI

Multiple Table Inheritance (MTI)

Next we turn our attention to a very different case. We have defined three different methods for payment—by credit card, by Paypal, or by entering a promotional code—and we can imagine over time there may be even more payment options. New contenders such as Google Checkout or Amazon’s payment system are likely future additions. Figure 10-4 shows this segment of our logical model.

er_1004Figure 10-4. Logical model for Payment and its subclasses

The physical models for these three classes is shown in Figure 10-5, along with all of their attributes. What we find when we look at these three payment subclasses is that they don’t share much information in common between them. A Paypal payment might have the user’s email address, as well as some returned authorization information from Paypal that means the payment was successful. A free ticket purchased via a promotional code might have a foreign key reference to the pertinent promotion (not shown). And standard credit card payment records will have all the credit card information necessary to process the transaction, and also any returned authorization information.

er_1005Figure 10-5. Physical model for our three payment types; no inheritance needed in the physical model

While the three subtypes of CreditCardPayment shared much in common, and were therefore ripe for single table inheritance, these three disparate payment types don’t share much in common at all. They share the transaction amount, whether the payment was processed successfully or not, and if we stretch ourselves, the record id as well. Therefore, we keep these tables separate, but each model class will continue to inherit from the Payment class, so that we can continue to take advantage of the benefits of inheritance.

One problem we run in to, though easily solved, is that Rails assumes the table name associated with a class is based on the first class in the hierarchy to descend from ActiveRecord::Base. In this case it would be assumed that a table called payments existed, and that it contained a type column. This assumption is what makes single table inheritance work. When using multiple table inheritance, we need to tell Rails to use each subclass’s own table, using the set_table_name directive in each subclass:

class Payment < ActiveRecord::Base
end

class PromotionalPayment < Payment
  set_table_name 'promotional_payments'
end

class CreditCardPayment < Payment
  set_table_name 'credit_card_payments'
end

class PaypalPayment < Payment
  set_table_name 'paypal_payments'
end

Our initial goal was to set up a relationship between the orders table and these payment types without using :polymorphic => true. Instead, we add to the orders table references to each of these tables independently. Example 10-1 shows the orders table with these references, each of which can support a true database-level constraint, unlike an application-level polymorphic assocation. Note that unlike most other references we have defined in this book, promotional_payment_id, credit_card_payment_id, and paypal_payment_id must all be nullable, because only one of them should contain an actual reference at any one time. The challenge then, is how to make sure only one of the references is not null. We accomplish this with XOR on columns.

Example 10-1. Orders table with references to each subclass in a multiple table inheritance hierarchy

create table orders (
  confirmation_code varchar(16) not null
    check (length(confirmation_code) > 0),
  movie_showtime_id integer not null
    references movie_showtimes(id),
  movie_id integer not null,
  theatre_id integer not null,
  room varchar(64) not null,
  start_time timestamp with time zone,
  credit_card_payment_id integer
    references credit_card_payments(id),
  promotional_payment_id integer
    references promotional_payments(id),
  paypal_payment_id integer
    references paypal_payments(id),
  primary key (confirmation_code),
  foreign key (movie_id, theatre_id, room, start_time)
    references movie_showtimes (movie_id, theatre_id, room, start_time) initially deferred
);

XOR on Columns

XOR, pronounced “ex or” is also known as exclusive or. It is a mathematical operator, meant to be applied to two inputs, which tests that one of the two values is true, but not both. Table 10-1 is a truth table for XOR.

Table 10-1. Truth table for two-value XOR
A	B	A XOR B
False	False	False
True	False	True
False	True	True
True	True	False

We could write XOR simply in Ruby:

def xor(a, b)
  (a || b) && !(a && b)
end

It would also not be difficult to write this as a database check constraint. If we were only trying to guarantee the relationship that only one of paypal_payment_id or promotional_payment_id were not null, forgetting for a moment about credit_card_payment_id, we could create a constraint as follows:

alter table orders add constraint paypal_or_promotional_payment_xor
  check(
   (paypal_payment_id is not null or promotional_payment_id is not null)
    and not
   (paypal_payment_id is not null and promotional_payment_id is not null)
  );

However, the situation gets a bit more complicated when we move to a relationship with more than two columns. Strictly speaking, the mathematical definition of XOR for more than two values is not exactly what you might expect. Traditionally in mathematical contexts, XOR beyond two inputs is true if an odd number of values are true. For our purposes, we want a real exclusive or, meaning one and only one value is true. Therefore, for three values, a truly exclusive XOR would look like this in Ruby:

def xor3(a, b, c)
  (a || b || c) &&
  !(a && b) && !(a && c) && !(b && c)
end

As we go up in the number of parameters, the first part of our expression, (a || b || c), where we check that at least one value is true, expands linearly with the number of parameters. However, the second half of the expression, where we check that not more than one value is true, expands mathematically as the number of parameters choose two, written as . This means that we need to enumerate every pair that exists in the set, and that can be cumbersome to write. Cumbersome code tends to lead to coding errors, so we’d like to avoid that.

Instead we note a special property. If we convert values that are not null to 1, and values that are null to 0, and add these up, the property we want to maintain is true if the sum of these values is equal to 1.

We can convert an expression that evaluates to true or false to numerical values in the database using an integer cast. In Postgres, we would write this as:

(expression)::integer

An expression that evaluates to true is equal to 1 when cast to an integer, and a false expression evaluates to 0. So for three columns, only one of which should be null, we can cast the expressions to integers, and add them up. If the sum is equal to 1, then we know that only one column is not null. We add the following constraint to the orders table using this technique:

alter table orders add constraint payment_xor check(
  (credit_card_payment_id is not null)::integer + 
  (paypal_payment_id      is not null)::integer + 
  (promotional_payment_id is not null)::integer = 1
);

We now have the referential integrity we desired in the data layer. The next step would be to write database unit tests, which is left as an exercise. We’ll skip ahead here to our next goal: easy-to-use polymorphic associations at the application layer, built atop our solid data model.

Elegant MTI in Rails

The physical data model we are left with now has three physical associations from our orders table to the various payment tables, shown in Figure 10-6. We would normally express such a relationship in Rails by creating a belongs_to relationship for each one:

class Order < ActiveRecord::Base
  belongs_to :credit_card_payment
  belongs_to :promotional_payment
  belongs_to :paypal_payment
end

Even though the physical model has three independent connections, application code is where we want to deal with objects the way our logical model dictates the objects fit together. Looking back at our logical model in Figure 10-2, we see that an Order object has a Payment object. One nice aspect of the built-in polymorphic associations is that this relationship is created for you automatically; you can assign instances of any payment type directly into a attribute called payment, rather than having to explicitly assign into the attribute of the correct type, as we would have to do with three separate belongs_to declarations we wrote above.

er_1006Figure 10-6. Physical association of the orders table with our three payment types

Luckily, we can get the behavior we want; we just need to create it ourselves. We’ll use inheritance to accomplish this. The first step is to define our Payment class. It doesn’t do much. It’s purpose is to collect the subclasses somewhere; even though duck typing means our payment types don’t have to descend from Payment to act like payments, we’ll use the inheritance relationship to facilitate our implementation of MTI. The Payment class, therefore, is simply a shell:

class Payment < ActiveRecord::Base
end

require 'credit_card_payment'
require 'paypal_payment'
require 'promotional_payment'

Note that we also loaded each subclass using the require command. Generally, it’s a bad idea for a parent class to know about its subclasses, but due to the way Rails loads code—only when it’s needed—the subclasses may not get loaded in time for our needs here. We’ll discuss how to get around this problem later in the book.

Example 10-2 shows how we go about creating an accessor, payment, which gives us the right object, regardless of which association is actually in use. Note that we could have written this method in a much simpler way, but we wrote it in a generic fashion so it could be applied to other classes, or be recast as a plugin, without much change. Writing it generically also allows us to introduce some advanced features of Ruby and Rails.

Example 10-2. Polymorphic accessor for multiple table inheritance

class Order < ActiveRecord::Base
  # assocations would go here...

  def payment
    # for a given class, returns the appropriate symbol
    # to pass to the ActiveRecord method reflect_on_association
    def reflection_symbol(klass)
      klass.to_s.split("::").last.underscore.to_sym
    end

    # for all subclasses of the given base class, returns a
    # list of defined associations within the current class
    def association_methods(mti_base_class)
      Object.subclasses_of(mti_base_class).collect{|p|
        assoc = self.class.reflect_on_association(reflection_symbol(p))
        assoc ? assoc.name : nil
      }.compact
    end
 
    # invoke each association method and return the first
    # that is not null
    association_methods(Payment).collect{|a|
      self.send a
    }.inject do |a, b| 
      a || b
    end
  end
end

The above example of our payment accessor is rather complex, so we’ll go through each piece in turn. If you are unfamiliar with any of the built-in Ruby or Rails methods introduced here, check the API reference sidebar.

API Reference
Here are some Ruby methods:
array.collect {|item| block }an_array

Invokes block once for each element of self. Creates a new array containing the values returned by the block:

 a = [ "a", "b", "c", "d" ]
 a.collect {|x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
 a                          #=> ["a", "b", "c", "d"]
array.compactan_array

Returns a copy of self with all nil elements removed:

 [ "a", nil, "b", nil, "c", nil ].compact 
                            #=> [ "a", "b", "c" ]
enum.inject {| memo, obj | block }an object

Combines the elements of enum by applying the block to an accumulator value (memo) and each element in turn. At each step, memo is set to the value returned by the block. The first form lets you supply an initial value for memo. The second form uses the first element of the collection as the initial value (and skips that element while iterating).

 # Sum some numbers
 (5..10).inject {|sum, n| sum + n }  #=> 45
 # Multiply some numbers
 (5..10).inject(1) {|product, n| product * n } #=> 151200
And here are some Rails methods:
reflect_on_association(association)AssociationReflection

Returns the AssociationReflection object for the named association (use the symbol):

 # returns the owner AssociationReflection
 Account.reflect_on_association(:owner)
 # returns :has_many
 Invoice.reflect_on_association(:line_items).macro  
underscore(camel_cased_word)String

The reverse of camelize. Makes an underscored form from the expression in the string. Changes :: to / to convert namespaces to paths:

"ActiveRecord".underscore
    #=> "active_record"
"ActiveRecord::Errors".underscore
    #=> “active_record/errors"
Object.subclasses_of(*superclasses)[Class]

Returns an array containing the subclasses of the parameters. The superclasses themselves are returned as well.

The first point to note is that we can define methods inside other methods. Notice that within the payment method, we’ve defined two additional helper methods, reflection_symbol and association_methods. The reason we did this is to limit the scope of these methods. By defining them within the payment method, which is the only place we need them, these methods are only accessible within that method, and they won’t conflict with other methods that may have the same name. The more modules we mix in or plugins we use, the higher the risk of conflict and the greater the need for scoping.

Next let’s look at the helper method reflection_symbol. This method would turn a class variable, such as CreditCardPayment, into the symbol :credit_card_payment. In this method, we’ve chained a number of methods together. The single line of code does quite a lot of things. Let’s break it down:

  1. Starting with the input class, referred to locally within the method as klass, we cast the class name to a string with to_s, so that we can use string operations.
  2. We then account for classes that are within modules, e.g., SomeModule::CreditCardPayment. The split method cuts our string everywhere that the split text is found, and returns an array, [“SomeModule”, “CreditCardPayment”].
  3. The last method returns the last element of the array, which is the string representation of the class name we are interested in, “CreditCardPayment”.
  4. The underscore method transforms camel-case text into lowercase text separated by underscores everywhere there was a capital letter: “CreditCardPayment” becomes “credit_card_payment”.
  5. Finally, we turn this back into a symbol, using to_sym. The result is :credit_card_payment, which is the input expected by the Rails method reflect_on_association, which we’ll deal with next.

The purpose of the association_methods method is to give us a list of the accessors for each subclass of the passed-in class, in this case Payment. Based on how we defined our associations for the three subtypes, we know that the methods are named credit_card_payment, promotional_payment, and paypal_payment. We could write a single-use method that returns this list. Instead of writing a single-use method that we would have to constantly update as we make changes, we instead write a general function that automatically gives us all the right associations. We don’t want our code to break if we change the association definitions, or add or remove subclasses.

This method also looks complicated at first glance, but we’ll examine it in detail as well:

  1. We start with the input, mti_base_class, which is the base class in the polymorphic association, Payment in this case.
  2. Rails extends the class Object, which is the base class of all classes in Ruby, with the method subclasses_of. As its name implies, this method returns all subclasses of the passed in class. This is the reason we needed to preload the subclasses of Payment ahead of time; if they aren’t preloaded, this method returns an empty array. Passing Payment to subclasses_of produces [CreditCardPayment, PromotionalPayment, …]. Note that this list includes all of the subclasses of CreditCardPayment, too.
  3. We then utilize the collect method on this array of classes, which allows us to run a block of code for each element. collect returns a new array where each element is the result of the code block run against each input element.
  4. Inside the code block, we run the ActiveRecord method reflect_on_association, which returns an object containing all the information Rails knows about the given association. For example, if we had given a custom name to the association, or if the foreign key is nonstandard, the information is contained in this return value. An example return object is shown in Example 10-3.
  5. We then check to see if there actually was an association defined at all. For example, the various subclasses of CreditCardPayment are included up to this point, but we didn’t define an explicit association for each of them; they were included implicitly in the association with CreditCardPayment itself. If there was an association found, we call name on the association data, which gives us the name by which we can access the associated object. Otherwise, we return nil.
  6. Finally, we call compact on the result, so the nil values are removed. This method returns [credit_card_payment, promotional_payment, paypal_payment].
Example 10-3. A return value from reflect_on_association
 Order.reflect_on_association(:credit_card_payment)
=> #

Finally, we can deal with the remaining code in the payment method, which makes use of these two helper methods:

  1. We start with the result of association_methods, which are the accessors for each association, as explained above.
  2. For each one, we call that method on the current object using send. The result is the associated object, if there is one, or nil if there isn’t.
  3. We used collect, so the result is a new array of objects. All should be nil except for the association that actually exists.
  4. We could get the non-nil value out in a variety of ways. Above we saw compact, which would give us the non-nil values. Instead we introduce inject, which allows us to apply an operator to the values of an array, two items at a time. Injecting || as we do here first applies || to the first two elements in the array. It then applies || to the first result and the third element, and so on. In other words, ((a || b) || c) || …). Evaluating this expression returns the first non-nil value encountered. This is the associated payment object that we are looking for.

It’s another tribute to Ruby’s compactness that it took more than two pages to explain a few lines of code. As you become more familiar with the entirety of the Ruby and Rails APIs and get comfortable using Ruby’s powerful syntax, you can really say a whole lot with very little code.

In Example 10-4, we define the complimentary method, payment=, which assigns the argument to the correct association assignment method. We won’t go through it in detail; based on the explanation of the payment method, you should be able to understand it.

Example 10-4. Polymorphic assignment for multiple table inheritance

def payment=(p)
  def reflection_symbol(klass)
    klass.to_s.split("::").last.underscore.to_sym
  end

  def reflection_assignment_method(klass)
    Order.reflect_on_association(reflection_symbol(klass.class)).name.to_s + "="
  end

  self.send reflection_assignment_method(p.class), p
end

Factory Classes

Let’s return to the Payment class. It’s a very sad class, completely empty and with next to no purpose. We can give it a purpose by turning it into a factory class, utilizing another pattern in the famous Gang of Four Design Patterns book (Addison-Wesley), and making our MTI solution even more powerful and DRY.

A factory class is a class that has a constructor that returns instances of the correct subclass based on the inputs. Currently, if we have web forms with a radio selector for the payment type (Paypal, credit, or promotional), we’d have to write a case statement in every place we’re processing the input in order to create an instance of the correct type. Same goes for the credit card type (Visa, American Express, or MasterCard).

Instead, we can localize this logic in the Payment class itself. Any code that needs a new payment object can pass in the appropriate information to the factory method and out will come an object of the correct type.

Below we define a constructor, new_payment, for the Payment class:

class Payment < ActiveRecord::Base
  def self.new_payment(payment_type, credit_card_type)
    case type
    when 'paypal'
      PaypalPayment.new
    when 'promotional'
      PromotionalPayment.new
    when 'credit_card'
      CreditCardPayment.new_payment credit_card_type
    end
  end
end

Notice that if the payment is a credit card payment, we defer to a constructor within the CreditCardPayment class. That constructor might look like this:

class CreditCardPayment < Payment
  # other code ...
  def self.new_payment(credit_card_type)
    case credit_card_type
    when 'american_express'
      AmericanExpress.new
    when 'visa'
      Visa.new
    when 'master_card'
      MasterCard.new
    end
  end
end

Speaking of DRY, these classes are also a good place to keep a list of the allowable inputs to the constructor that can be used to build the dropdowns or radio button lists in our views. For example, the following array can be passed to options_for_select to create a credit card drop-down:

CREDIT_CARD_TYPES_FOR_SELECT = [
  ['visa', 'Visa'],
  ['american_express', 'AmericanExpress'],
  ['master_card', 'MasterCard']
]

We can then create a select box with the appropriate values for our constructor by calling select_tag and options_for_select like this:

<%= select_tag(
 'credit_card_type',
 options_for_select(CreditCardPayment::CREDIT_CARD_TYPES_FOR_SELECT)
) %>

Exercises

  1. Write unit tests for the orer_payment_xor constraint. Ensure that any order with zero or multiple payments is disallowed by the data layer, but an order with a single payment is accepted.
  2. Following the example laid out in this chapter, extend the MTI plugin with a has_many MTI association method.
  3. What other methods are needed to create a complete MTI plugin? How would you implement them?

Refactor Steps

These sections break down the refactoring steps for you.

Refactoring STI

  1. Examine each STI table in your data model. For each, determine how much data is really shared between the subclasses.
  2. If the answer is “not much,” or if different subclasses call for different constraints that are difficult to reconcile with each other, proceed with this multiple table inheritance refactoring.
  3. Create a separate table for each class, custom fit to the class’s needs.
  4. Maintain the inheritance relationship in the model classes, but explicitly use the set_table_name directive in each class.
  5. In the associated classes, replace built-in association declarations with the MTI-flavored ones developed in this chapter.
  6. Run your tests.

    Refactoring: polymorphic => true

  1. Make a list of all the referenced types. You can find this list with the following SQL query, assuming the polymorphic type column is called {foreign_table}_type and the association exists in a table called widgets:
    select distinct foreign_table_type from widgets;
    
  2. For each table referenced, add a column to the table with the polymorphic association (widgets here) that references the target table directly. The column should be nullable:
    alter table widgets
      add constraint specific_foreign_table_fkey
      (id) references specific_foreign_table(id);
    
  3. For each table, set the reference. Keep in mind that your old data may have invalid references, which you should avoid copying:
    update widgets
       set specific_foreign_table_id = foreign_table_id
     where foreign_table_type = 'SpecificForeignTable'
       and exists (
    select true
      from specific_foreign_table sft
     where sft.id = foreign_table_id
    );
    
  4. Once steps 2 and 3 have been repeated for all referenced tables, decide what to do with orphaned references. You may want to update them to a valid state, or simply delete them. To delete them, delete rows where all of the specific foreign key references are null:
    delete from widets where
     specific_foreign_table_1_id is null and
     specific_foreign_table_2_id is null and
     ...
     specific_foreign_table_n_id is null;
    
  5. Add an XOR constraint to ensure future records will not become orphaned or invalid:
    alter table widgets
      add constraint sft_1_thru_n_xor check (
      (specific_foreign_table_1_id is not null)::integer +
      (specific_foreign_table_2_id is not null)::integer +
      ... +
      (specific_foreign_table_n_id is not null)::integer) = 1);
    
  6. Create a base class based on the original polymorphic association name, and derive all referenced classes from this class.
  7. Use the MTI plugin developed in this chapter to create the appropriate associations in the widget class:
    belongs_to_mti :base_class_name
    
  8. Run your tests.
Chapter 9 : Guaranteeing Complex Relationships with Triggers
Chapter 11 : View – Backed Models
Advertisements