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 by
on 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