ActiveRecord and self-join association

Objective

Build a file system model layer, creating files and directories.

Prerequisites

ruby >= 2.5

Creating our rails project

We are going to skip test(We will use rspec), turbolinks, action-mailer and coffee-script

rails new click-file -T –skip-turbolinks –skip-action-mailer –skip-coffee

Configurations

Tt’s time to add yarn dependencies

yarn add jquery

app/assets/javascripts/application.js

//= require jquery

Let’s create our model FileSystem

rails generate model file_system parent:references name file:boolean

Don’t forget to add index to the column name and put a default in the boolean column.

# db/migrate/20181204185142_create_file_systems.rb
class CreateFileSystems < ActiveRecord::Migration[5.2]
  def change
    create_table :systems do |t|
      t.references :parent, foreign_key: true
      t.string :name, index: true
      t.boolean :file, null: false, default: true

      t.timestamps
    end
  end
end

Now we’ll edit our file_system.rb class

# app/models/file_system.rb
class FileSystem < ApplicationRecord
  # this is the self-join association
  belongs_to :parent, class_name: 'FileSystem', optional: true
  has_many :children, class_name: 'FileSystem', foreign_key: 'parent_id'

  scope :roots, -> { where('parent_id is null') }

  validate :only_folder_should_have_children

  alias_attribute :text, :name

  def children?
    children.exists?
  end

  def folder?
    !file
  end

  private

  def only_folder_should_have_children
    if file? && folder.try(:file?)
      errors.add(:parent_id, 'A file cannot have children')
    end
  end
end

Let’s explain what we just did

belongs_to :folder

A file or directory normally belongs to another folder. Example: a music_directory can have two files into it.

- my_music (directory)
  - fear_of_the_dark.mp3 (file, parent=my_music)
  - two_minutes_to_midnight.mp3 (file, parent=my_music)
belongs_to :parent, class_name: 'FileSystem', optional: true

A folder/file belongs to a Model (FileSystem - itself) Here I used optional: true because I ran this project in Rails 5 and in this Rails version all relations are required. But not all files should have parents.

has_many :children

As a folder/directory our model can have children, that are sub-folders or files into it. With has_many we did a relation with its children, in other words, models who has a parent (parent_id)

has_many :children, class_name: 'FileSystem', foreign_key: 'parent_id'

your FileSystemController

# app/controllers/file_systems_controller.rb
class FileSystemsController < ApplicationController
  def index; end

  def show_files
    file_systems = FileSystem.roots
    render json: file_systems
  end
end

Routes

Rails.application.routes.draw do
  root to: 'file_systems#index'

  resources :file_systems, only: :index
  get '/file_systems/show_modal/:id', to: 'file_systems#show_modal', as: :show_modal_upload
end

The FrontEnd

In the frontend as I said above, we will use JS-Tree CND

add these scripts to your app/views/layouts/application.html.erb

<head>
  <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.5/themes/default/style.min.css" />
  <script src="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.5/jstree.min.js"></script>
</head>

open your app/views/file_systems/index.html.erb

show_files_file_systems_path is your route path

<div id="js_tree"></div>
<script>
  $(function() {
    $('#js_tree').jstree({
      'core' : {
        'data' : {
          "url" : "<%= show_files_file_systems_path %>",
          "dataType" : "json"
        }
      }
    });
  });
</script>

open http://localhost:3000

View post on imgur.com

You can see and run the whole project with specs on GitHub - ClickFile

References

Check this project on GitHub
Active Storage
Active Record Basics