Say I'm building a zoo application and I have a model implemented for each animal type: Mammal, Bird, Reptile, Invertebrate, Fish, and Amphibian. All these have a title, description, and image field but they may have some other fields as well. How would I be able to display a list of all animals alphabetically from one controller? I would like for them to use their own partial.
CodePudding user response:
How would I be able to display a list of all animals alphabetically from one controller?
If they're all in separate tables, the options aren't great.
- Load them all into memory and sort. This might work if there aren't that many animals.
- Make a SQL view which does a SQL union to make them look like one table and do an
order byon that view. This is messy and inefficient.
An alternative is to put all animals into a single table and use Single Table Inheritance to differentiate the types into subclasses.
$ bin/rails generate model animal type:string title:string description:string
class Animal < ApplicationRecord
def eat
...
end
def drink
...
end
def make_little_animals
...
end
end
class Mammal < Animal
def lactate
end
end
class Bird < Animal
def make_little_animals
lay_egg
end
def lay_egg
...
end
def fly?
...
end
end
All three will be stored in animals. Animal.order(:title) will return all Animals ordered by their title. But those with a type of Bird will load as Bird objects. Those with a type of Mammal will load as Mammal objects. Bird.all will return only Animals with the type Bird.
Consider whether you need subclasses at all. If they don't have any different functionality, add a column for their taxonomy and make some scopes.
class Animal < ApplicationRecord
scope :mammals -> { where(taxonomic_class: :mammalia }
scope :birds -> { where(taxonomic_class: :aves }
def eat
...
end
def drink
...
end
def make_little_animals
...
end
end
Then you can ask for all animals Animal.order(:title) or just birds Animal.birds.order(:title).
I would suggest starting there. You can always split it up into subclasses later.
CodePudding user response:
I found two great options but still not sure if I will run into issue displaying as lists:
1 - Create a multiple-table inheritance as shown here.
class Animal < ActiveRecord::Base
def eat
end
end
class Bird < Animal
set_table_name "birds"
def fly
end
end
class Mammal < Animal
set_table_name "mammals"
def run
end
end
class Fish < Animal
set_table_name "fish"
def swim
end
end
class CreateMammal < ActiveRecord::Migration
def change
create_table :mammals do |t|
t.integer :num_of_legs
# ... more column fields #
t.timestamps
end
end
end
class CreateFish < ActiveRecord::Migration
def change
create_table :fish do |t|
t.integer :num_of_fins
# ... more column fields #
t.timestamps
end
end
end
class CreateBird < ActiveRecord::Migration
def change
create_table :birds do |t|
t.float :wing_span
# ... more column fields #
t.timestamps
end
end
end
view raw
2 - Create something like this product example shown here.
class ActiveRecord::Base
def self.acts_as_product
include Sellable
end
end
class ProductProperties < ActiveRecord::Base
belongs_to :sellable, :polymorphic => true, :dependent => :destroy
validates_presence_of :title # for example
end
module Sellable
def self.included(base)
base.has_one :product_properties, :as => :sellable, :autosave => true
base.validate :product_properties_must_be_valid
base.alias_method_chain :product_properties, :autobuild
base.extend ClassMethods
base.define_product_properties_accessors
end
def product_properties_with_autobuild
product_properties_without_autobuild || build_product_properties
end
def method_missing(meth, *args, &blk)
product_properties.send(meth, *args, &blk)
rescue NoMethodError
super
end
module ClassMethods
def define_product_properties_accessors
all_attributes = ProductProperties.content_columns.map(&:name)
ignored_attributes = ["created_at", "updated_at", "sellable_type"]
attributes_to_delegate = all_attributes - ignored_attributes
attributes_to_delegate.each do |attrib|
class_eval <<-RUBY
def #{attrib}
product_properties.#{attrib}
end
def #{attrib}=(value)
self.product_properties.#{attrib} = value
end
def #{attrib}?
self.product_properties.#{attrib}?
end
RUBY
end
end
end
protected
def product_properties_must_be_valid
unless product_properties.valid?
product_properties.errors.each do |attr, message|
errors.add(attr, message)
end
end
end
end
class Tee < ActiveRecord::Base
acts_as_product
end
class Pen < ActiveRecord::Base
acts_as_product
end
