Home > Blockchain >  How can I display a list of multiple models from one controller?
How can I display a list of multiple models from one controller?

Time:12-11

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.

  1. Load them all into memory and sort. This might work if there aren't that many animals.
  2. 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
  • Related