36

I'm getting this error when trying to create a new Product in my Rails application.

Invalid single-table inheritance type: Movie is not a subclass of Product

How can I resolve this?

enter image description here

controller

class ProductsController < ApplicationController
  before_action :set_product, only: [:show, :edit, :update, :destroy]

  # GET /products
  # GET /products.json
  def index
    @products = Product.all
  end

  # GET /products/1
  # GET /products/1.json
  def show
  end

  # GET /products/new
  def new
    @product = Product.new
  end

  # GET /products/1/edit
  def edit
  end

  # POST /products
  # POST /products.json
  def create
    @product = Product.new(product_params)

    respond_to do |format|
      if @product.save
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
        format.json { render action: 'show', status: :created, location: @product }
      else
        format.html { render action: 'new' }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /products/1
  # PATCH/PUT /products/1.json
  def update
    respond_to do |format|
      if @product.update(product_params)
        format.html { redirect_to @product, notice: 'Product was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /products/1
  # DELETE /products/1.json
  def destroy
    @product.destroy
    respond_to do |format|
      format.html { redirect_to products_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_product
      @product = Product.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def product_params
      params.require(:product).permit(:title, :artist, :type, :release_date, :category, :publisher, :format, :description, :sale_price, :rental_price)
    end
end

migration

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :title
      t.string :artist
      t.string :type
      t.date :release_date
      t.string :category
      t.string :publisher
      t.string :format
      t.text :description
      t.decimal :sale_price
      t.decimal :rental_price

      t.timestamps
    end
  end
end
Antarr Byrd
  • 24,863
  • 33
  • 100
  • 188
  • 1
    make sure you have Movie class that extends Product – Muntasim Dec 03 '13 at 17:47
  • 1
    extending @Muntasim comment: check that you have in your models: `class Product < ActiveRecord::Base` and `class Movie < Product` – MrYoshiji Dec 03 '13 at 17:49
  • So I have go create a new Model + Controller anytime I want a new Product type? @MrYoshiji – Antarr Byrd Dec 03 '13 at 17:50
  • 1
    If your `Movie` class does not have anything special to do except categorization then you need not use STI. Otherwise you have to have all possible classes defined and you `Type` field should be select box instead of free text. – Muntasim Dec 03 '13 at 17:55
  • 1
    Nope, you can just use the ProductsController and use a params[:type] to handle a certain kind of Product -- for the model, yes you'll have to create a new model for each new type of Product. Do you want me to post an answer with some examples @AntarrByrd ? – MrYoshiji Dec 03 '13 at 17:56
  • @MrYoshiji Please do. – Antarr Byrd Dec 03 '13 at 18:01

4 Answers4

101

You should not use the type keyword as the column name because it is a reserved word for ActiveRecord.


But if you really want to use it, for any reason (like if you don't have control on the DB structure), here is what you should do:

First, make sure your Movie model inherits from the (false-)"abstract" model Product:

class Product < ActiveRecord::Base
  TYPES = %w( Movie )
  before_save :set_type
  validates :type, presence: true, :inclusion => { :in => TYPES }

  def set_type
    raiser "You must override this method in each model inheriting from Product!"
  end

  # ...

class Movie < Product

  def set_type # If you don't implement this method, an error will be raised
    self.type = 'Movie'
  end

And then in your ProductsController you can manage (CRUD) all kind of products.


To add a new type of product: you just have to define a new Model inheriting from Product, implement it's set_type method and add the type in the product's Constant:

class Book < Product
  def set_type
    self.type = 'Book'
  end
  #...

class Product < ActiveRecord::Base
  TYPES = %w( Movie Book )
MrYoshiji
  • 54,334
  • 13
  • 124
  • 117
  • When using this method it seems I still have create a Movies controller that inherits from from the products controller – Antarr Byrd Dec 03 '13 at 19:32
  • If you are using the `rails generate model` command it might create a Controller for you, but you can delete it (and the corresponding routes) and just use the ProductsController. – MrYoshiji Dec 03 '13 at 19:38
  • I'm doing it manually, I get an error say `undefined method 'movie_path'`. When add movies to routes.rb it complains about an unitialized controller – Antarr Byrd Dec 03 '13 at 19:45
  • 1
    You have to use product_path(movie), not movie_path(movie) – MrYoshiji Dec 03 '13 at 19:53
33

Add self.inheritance_column = nil to your model. type is reserved.

DDDD
  • 3,790
  • 5
  • 33
  • 55
32

If you don't intend to create a model for your value for 'type', then what is likely happening is 'type' is a reserved word in ActiveRecord.

See http://en.wikibooks.org/wiki/Ruby_on_Rails/ActiveRecord/Naming

"type - This is only used when you have single table inheritance and must contain a class name"

h4xnoodle
  • 1,038
  • 1
  • 11
  • 14
  • I was bitten by this exact same problem. The following json fragment : { content = "some content"; type = image; }; gave the error : > Invalid single-table inheritance type: image is not a subclass of Foo::Bar – Guillaume Laurent Apr 15 '14 at 15:03
  • Yep, don't name your column :type unless you're going polymorphic. – Erik Trautman Jun 04 '14 at 19:40
1

Another way is to name the column differently (product_type would be fine here), and add an attribute alias alias_attribute :type, :product_type in your model definition.

That way you can use @product.type safely, because Rails will substitute product_type when you read from or write to the type attribute.

Tested with Rails 4.1.2.

Goulven
  • 777
  • 9
  • 20