Skip to content

Instantly share code, notes, and snippets.

@oojikoo-gist
Created April 17, 2015 03:20
Show Gist options
  • Select an option

  • Save oojikoo-gist/97ffcf9f73086d2be232 to your computer and use it in GitHub Desktop.

Select an option

Save oojikoo-gist/97ffcf9f73086d2be232 to your computer and use it in GitHub Desktop.

Revisions

  1. oojikoo-gist created this gist Apr 17, 2015.
    133 changes: 133 additions & 0 deletions STI.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,133 @@
    # Single Table inheritance

    reference: [blog.thirst.co](http://blog.thirst.co/post/14885390861/rails-single-table-inheritance)

    In a nutshell, STI allows you to create subclasses of a particular database table. Using a single table, you can cast rows to specific objects that extend the base model.

    ## how to create STI relationships in Rails
    Lets say we have a model Computer

    ```ruby
    class Computer < ActiveRecord:Base
    # in app/models
    # Fields:
    # String name
    # String owner
    # String manafacturer
    # String color

    def default_browser
    "unknown!"
    end
    end
    ```

    Now, we want to differentiate between Macs and PCs. It doesn’t really make sense to make a different table for each, since they both have pretty much the same columns. Instead we can create a new column, type, which tells Rails to use STI on Computer. Lets look at what the models might look like.

    ```ruby
    class Computer < ActiveRecord:Base
    # in app/models
    # Fields:
    # String name
    # String owner
    # String manafacturer
    # String color
    # String type

    def default_browser
    "unknown!"
    end
    end

    class Mac < Computer
    # in app/models
    # this is for Computers with type="Mac"
    before_save :set_color

    # Lets say all macs are silver, no point setting these ourselves
    def set_color
    self.color = "silver"
    self.manafacturer = "apple"
    end

    # Lets overwrite the default_browser method
    def default_browser
    "safari"
    end
    end

    class PC < Computer
    # in app/models

    # Lets overwrite the default_browser method
    def default_browser
    "ie =("
    end
    end
    ```

    Anytime Rails opens up the computer object, it looks for the subclass corresponding to type. For instance, type="CoolComputer" corresponds to model CoolComputer < Computer.

    ## How to user STI Models

    To create a new mac, you can do:

    ```ruby
    m = Mac.new
    m.name = "kunal's mac"
    m.owner = "kunal"
    m.save
    m # => #<Mac id: 1, name: "kunal's mac", owner: "kunal", manafacturer: "apple", color: "silver", type: "Mac", ...>
    ```

    Whats even cooler is ActiveRecord queries. Lets say we want all the computers


    ```
    Computer.all # => [#<Mac id: 1, name: "kunal's mac", owner: "kunal", manafacturer: "apple", color: "silver", type: "Mac", ...>, #<Mac id: 2, name: "anuj's mac", owner: "anuj", manafacturer: "apple", color: "silver", type: "Mac", ...>, #<PC id: 3, name: "bob's pc", owner: "bob", manafacturer: "toshiba", color: "blue", type: "PC", ...>]
    ```

    Yup, it automatically gives you the correct objects! You can find out the type of a particular object by calling .type, is_a? or .class

    ```ruby
    Computer.first.type == Mac # true
    Computer.first.is_a? Mac # true
    Computer.first.class == Mac # true
    ```

    If we only want Macs, we can do

    ```
    Mac.all
    ```


    ### Custom Inheritance Column

    If you want to use another column instead of type to use for STI, you can simply add this to the top of your model:

    ```
    set_inheritance_column 'whatever_you want'
    ```

    *Note: If you have a database column named type, you can turn off Single Table Inheritance by changing the inheritance column to something other than type.*

    ### Organizing This in Rails

    After using STI, I ended up with a bloated models folder because all of the many custom sub models I created were in the models folder. To solve this, I created a folder in models to store all of my computer specific models

    ```
    * app
    * models
    * computer.rb
    * computers
    * pc.rb
    * mac.rb
    ```

    Rails doesn’t automatically open subfolders in the models folder, so I added in config/application.rb:

    ```ruby
    # Load Subfolder Models
    config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]
    ```