From f341f9759af2d3b46c1dfef5f83df343282c5af2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Gaya?= <sebastien.gaya@gmail.com>
Date: Mon, 20 Dec 2021 12:17:47 +0100
Subject: [PATCH] nested form for education program members

---
 Gemfile                                       |  1 +
 Gemfile.lock                                  |  2 +
 app/assets/javascripts/admin.js               |  1 +
 .../administration/members_controller.rb      |  2 +-
 .../website/authors_controller.rb             |  4 +-
 .../admin/education/programs_controller.rb    |  3 +-
 .../admin/education/teachers_controller.rb    |  4 +-
 .../admin/research/researchers_controller.rb  |  4 +-
 app/models/education/program.rb               |  6 +++
 app/models/education/program/member.rb        | 25 +++++++++++
 app/models/university/with_administration.rb  |  2 +-
 .../website/posts/_form.html.erb              |  2 +-
 .../admin/education/programs/_form.html.erb   | 41 +++++++++++++++++--
 .../research/journal/articles/_form.html.erb  |  2 +-
 config/locales/education/en.yml               |  2 +
 config/locales/education/fr.yml               |  2 +
 ...085352_create_education_program_members.rb | 11 +++++
 db/schema.rb                                  | 14 ++++++-
 test/fixtures/education/program/members.yml   | 32 +++++++++++++++
 test/models/education/program/member_test.rb  | 28 +++++++++++++
 20 files changed, 172 insertions(+), 16 deletions(-)
 create mode 100644 app/models/education/program/member.rb
 create mode 100644 db/migrate/20211220085352_create_education_program_members.rb
 create mode 100644 test/fixtures/education/program/members.yml
 create mode 100644 test/models/education/program/member_test.rb

diff --git a/Gemfile b/Gemfile
index ea196db37..13a352d2e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -37,6 +37,7 @@ gem 'front_matter_parser'
 gem 'two_factor_authentication', git: 'https://github.com/noesya/two_factor_authentication.git'
 # gem 'two_factor_authentication', path: '../two_factor_authentication'
 gem 'curation'#, path: '../../arnaudlevy/curation'
+gem 'nested_form'
 
 # Front
 gem 'jquery-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index 460669c3d..42f72deee 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -263,6 +263,7 @@ GEM
     multipart-post (2.1.1)
     mustermann (1.1.1)
       ruby2_keywords (~> 0.0.1)
+    nested_form (0.3.2)
     nesty (1.0.2)
     nio4r (2.5.8)
     nokogiri (1.12.5)
@@ -437,6 +438,7 @@ DEPENDENCIES
   kaminari
   listen (~> 3.3)
   mini_magick
+  nested_form
   octokit
   pg (~> 1.1)
   puma
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index 7d66de99b..8d5f8c633 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -12,6 +12,7 @@
 //= require trix
 //= require sortablejs/Sortable
 //= require slug/slug
+//= require jquery_nested_form
 //= require_self
 //= require_tree ./admin/commons
 //= require_tree ./admin/plugins
diff --git a/app/controllers/admin/administration/members_controller.rb b/app/controllers/admin/administration/members_controller.rb
index b683ef4b2..d17fe8e7f 100644
--- a/app/controllers/admin/administration/members_controller.rb
+++ b/app/controllers/admin/administration/members_controller.rb
@@ -1,7 +1,7 @@
 class Admin::Administration::MembersController < Admin::Administration::ApplicationController
   load_and_authorize_resource class: Administration::Member,
                               through: :current_university,
-                              through_association: :members
+                              through_association: :administration_members
 
   def index
     @members = @members.ordered.page(params[:page])
diff --git a/app/controllers/admin/communication/website/authors_controller.rb b/app/controllers/admin/communication/website/authors_controller.rb
index 83c09f2d0..bc2418e82 100644
--- a/app/controllers/admin/communication/website/authors_controller.rb
+++ b/app/controllers/admin/communication/website/authors_controller.rb
@@ -1,12 +1,12 @@
 class Admin::Communication::Website::AuthorsController < Admin::Communication::Website::ApplicationController
 
   def index
-    @authors = current_university.members.authors.accessible_by(current_ability).ordered.page(params[:page])
+    @authors = current_university.administration_members.authors.accessible_by(current_ability).ordered.page(params[:page])
     breadcrumb
   end
 
   def show
-    @author = current_university.members.authors.accessible_by(current_ability).find(params[:id])
+    @author = current_university.administration_members.authors.accessible_by(current_ability).find(params[:id])
     @posts = @author.communication_website_posts.ordered.page(params[:page])
     breadcrumb
   end
diff --git a/app/controllers/admin/education/programs_controller.rb b/app/controllers/admin/education/programs_controller.rb
index d928c482a..36c0b863a 100644
--- a/app/controllers/admin/education/programs_controller.rb
+++ b/app/controllers/admin/education/programs_controller.rb
@@ -83,7 +83,8 @@ class Admin::Education::ProgramsController < Admin::Education::ApplicationContro
       :featured_image, :featured_image_delete, :featured_image_infos,
       :prerequisites, :objectives, :duration, :registration, :pedagogy,
       :evaluation, :accessibility, :pricing, :contacts, :opportunities, :other,
-      :parent_id, school_ids: [], teacher_ids: []
+      :parent_id, school_ids: [], teacher_ids: [],
+      members_attributes: [:id, :role, :member_id, :_destroy]
     )
   end
 end
diff --git a/app/controllers/admin/education/teachers_controller.rb b/app/controllers/admin/education/teachers_controller.rb
index aac641dce..34f774cd4 100644
--- a/app/controllers/admin/education/teachers_controller.rb
+++ b/app/controllers/admin/education/teachers_controller.rb
@@ -2,7 +2,7 @@ class Admin::Education::TeachersController < Admin::Education::ApplicationContro
   before_action :get_teacher, except: :index
 
   def index
-    @teachers = current_university.members.teachers.accessible_by(current_ability).ordered.page(params[:page])
+    @teachers = current_university.administration_members.teachers.accessible_by(current_ability).ordered.page(params[:page])
     breadcrumb
   end
 
@@ -29,7 +29,7 @@ class Admin::Education::TeachersController < Admin::Education::ApplicationContro
   protected
 
   def get_teacher
-    @teacher = current_university.members.teachers.accessible_by(current_ability).find(params[:id])
+    @teacher = current_university.administration_members.teachers.accessible_by(current_ability).find(params[:id])
   end
 
   def breadcrumb
diff --git a/app/controllers/admin/research/researchers_controller.rb b/app/controllers/admin/research/researchers_controller.rb
index 9f51dadd4..e6a11401c 100644
--- a/app/controllers/admin/research/researchers_controller.rb
+++ b/app/controllers/admin/research/researchers_controller.rb
@@ -1,12 +1,12 @@
 class Admin::Research::ResearchersController < Admin::Research::ApplicationController
 
   def index
-    @researchers = current_university.members.researchers.accessible_by(current_ability).ordered.page(params[:page])
+    @researchers = current_university.administration_members.researchers.accessible_by(current_ability).ordered.page(params[:page])
     breadcrumb
   end
 
   def show
-    @researcher = current_university.members.authors.accessible_by(current_ability).find(params[:id])
+    @researcher = current_university.administration_members.authors.accessible_by(current_ability).find(params[:id])
     @articles = @researcher.research_journal_articles.ordered.page(params[:page])
     breadcrumb
   end
diff --git a/app/models/education/program.rb b/app/models/education/program.rb
index 6ad5acac4..207a259a2 100644
--- a/app/models/education/program.rb
+++ b/app/models/education/program.rb
@@ -47,6 +47,10 @@ class Education::Program < ApplicationRecord
              class_name: 'Education::Program',
              foreign_key: :parent_id,
              dependent: :destroy
+  has_many   :members,
+             class_name: 'Education::Program::Member',
+             dependent: :destroy,
+             inverse_of: :program
   has_and_belongs_to_many :schools,
                           class_name: 'Education::School',
                           join_table: 'education_programs_schools',
@@ -59,6 +63,8 @@ class Education::Program < ApplicationRecord
                           association_foreign_key: 'education_teacher_id'
   has_many :websites, -> { distinct }, through: :schools
 
+  accepts_nested_attributes_for :members, allow_destroy: true
+
   enum level: {
     first_year: 100,
     second_year: 200,
diff --git a/app/models/education/program/member.rb b/app/models/education/program/member.rb
new file mode 100644
index 000000000..c0c64f8ea
--- /dev/null
+++ b/app/models/education/program/member.rb
@@ -0,0 +1,25 @@
+# == Schema Information
+#
+# Table name: education_program_members
+#
+#  id         :uuid             not null, primary key
+#  role       :string
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  member_id  :uuid             not null
+#  program_id :uuid             not null
+#
+# Indexes
+#
+#  index_education_program_members_on_member_id   (member_id)
+#  index_education_program_members_on_program_id  (program_id)
+#
+# Foreign Keys
+#
+#  fk_rails_...  (member_id => administration_members.id)
+#  fk_rails_...  (program_id => education_programs.id)
+#
+class Education::Program::Member < ApplicationRecord
+  belongs_to :program, class_name: 'Education::Program', inverse_of: :members
+  belongs_to :member, class_name: 'Administration::Member'
+end
diff --git a/app/models/university/with_administration.rb b/app/models/university/with_administration.rb
index 2db83f980..313e80472 100644
--- a/app/models/university/with_administration.rb
+++ b/app/models/university/with_administration.rb
@@ -2,6 +2,6 @@ module University::WithAdministration
   extend ActiveSupport::Concern
 
   included do
-    has_many :members, class_name: 'Administration::Member', dependent: :destroy
+    has_many :administration_members, class_name: 'Administration::Member', dependent: :destroy
   end
 end
diff --git a/app/views/admin/communication/website/posts/_form.html.erb b/app/views/admin/communication/website/posts/_form.html.erb
index 9b7fddc98..057fb390d 100644
--- a/app/views/admin/communication/website/posts/_form.html.erb
+++ b/app/views/admin/communication/website/posts/_form.html.erb
@@ -27,7 +27,7 @@
           <%= f.input :published %>
           <%= f.input :published_at, html5: true %>
           <%= f.input :pinned %>
-          <%= f.association :author, collection: current_university.members.authors.ordered %>
+          <%= f.association :author, collection: current_university.administration_members.authors.ordered %>
           <%= f.association :categories,
                             as: :check_boxes,
                             collection: @website.list_of_categories.map { |category| [
diff --git a/app/views/admin/education/programs/_form.html.erb b/app/views/admin/education/programs/_form.html.erb
index 274caa93f..53d220d17 100644
--- a/app/views/admin/education/programs/_form.html.erb
+++ b/app/views/admin/education/programs/_form.html.erb
@@ -1,6 +1,6 @@
-<%= simple_form_for [:admin, program] do |f| %>
+<%= simple_nested_form_for [:admin, program] do |f| %>
   <div class="row">
-    <div class="col-md-4">
+    <div class="col-md-3">
       <div class="card flex-fill w-100">
         <div class="card-header">
           <h5 class="card-title mb-0"><%= t('education.program.main_informations') %></h5>
@@ -39,7 +39,7 @@
         </div>
       </div>
     </div>
-    <div class="col-md-8">
+    <div class="col-md-4">
       <div class="card flex-fill w-100">
         <div class="card-header">
           <h5 class="card-title mb-0"><%= t('education.program.useful_informations') %></h5>
@@ -55,6 +55,39 @@
         </div>
       </div>
     </div>
+    <div class="col-md-5">
+      <div class="card flex-fill w-100">
+        <div class="card-header">
+          <h5 class="card-title mb-0">Members</h5>
+        </div>
+        <div class="card-body">
+          <div id="js-program-members">
+            <%= f.fields_for :members, program.members, wrapper: false do |member_form| %>
+              <div class="row js-program-member fields">
+                <div class="col-md-4">
+                  <%= member_form.input :role, label: false, required: true, placeholder: t('activerecord.attributes.education/program/member.role') %>
+                </div>
+                <div class="col-md-5">
+                  <%
+                  base_members = current_university.administration_members
+                  list_of_members = base_members.administratives.or(base_members.teachers).ordered
+                  %>
+                  <%= member_form.association :member,
+                                              collection: list_of_members,
+                                              include_blank: "Sélectionnez un membre",
+                                              label: false,
+                                              required: true %>
+                </div>
+                <div class="col-md-3">
+                  <%= member_form.link_to_remove "Supprimer", class: button_classes_danger %>
+                </div>
+              </div>
+            <% end %>
+          </div>
+          <p><%= f.link_to_add "Ajouter un membre", :members, class: 'btn btn-xs btn-primary', data: { target: "#js-program-members" } %></p>
+        </div>
+      </div>
+    </div>
   </div>
   <div class="card flex-fill w-100">
     <div class="card-header">
@@ -72,7 +105,7 @@
           <%= f.input :evaluation, as: :rich_text_area %>
           <%= f.association :teachers,
                             as: :check_boxes,
-                            collection: current_university.members.teachers.ordered %>
+                            collection: current_university.administration_members.teachers.ordered %>
         </div>
       </div>
     </div>
diff --git a/app/views/admin/research/journal/articles/_form.html.erb b/app/views/admin/research/journal/articles/_form.html.erb
index 1ca5b93cb..c2131ffce 100644
--- a/app/views/admin/research/journal/articles/_form.html.erb
+++ b/app/views/admin/research/journal/articles/_form.html.erb
@@ -29,7 +29,7 @@
           <%= f.association :volume, collection: @journal.volumes, label: Research::Journal::Volume.model_name.human %>
           <%= f.input :published_at, html5: true %>
           <%= f.input :keywords, as: :text, input_html: { rows: 2 } %>
-          <%= f.association :researchers, collection: current_university.members.researchers.ordered, as: :check_boxes %>
+          <%= f.association :researchers, collection: current_university.administration_members.researchers.ordered, as: :check_boxes %>
         </div>
       </div>
     </div>
diff --git a/config/locales/education/en.yml b/config/locales/education/en.yml
index 7c0cd5413..4b6780bf2 100644
--- a/config/locales/education/en.yml
+++ b/config/locales/education/en.yml
@@ -33,6 +33,8 @@ en:
         registration: Modalités et délais d’accès
         schools: Schools with this formation
         teachers: Teachers
+      education/program/member:
+        role: Role
       education/school:
         address: Address
         city: City
diff --git a/config/locales/education/fr.yml b/config/locales/education/fr.yml
index 509c363e7..7c13e0b24 100644
--- a/config/locales/education/fr.yml
+++ b/config/locales/education/fr.yml
@@ -33,6 +33,8 @@ fr:
         registration: Modalités et délais d’accès
         schools: Écoles proposant cette formation
         teachers: Enseignants·es
+      education/program/member:
+        role: Rôle
       education/school:
         address: Adresse
         city: Ville
diff --git a/db/migrate/20211220085352_create_education_program_members.rb b/db/migrate/20211220085352_create_education_program_members.rb
new file mode 100644
index 000000000..e4ed85b5f
--- /dev/null
+++ b/db/migrate/20211220085352_create_education_program_members.rb
@@ -0,0 +1,11 @@
+class CreateEducationProgramMembers < ActiveRecord::Migration[6.1]
+  def change
+    create_table :education_program_members, id: :uuid do |t|
+      t.string :role
+      t.references :member, null: false, foreign_key: { to_table: :administration_members }, type: :uuid
+      t.references :program, null: false, foreign_key: { to_table: :education_programs }, type: :uuid
+
+      t.timestamps
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index dd5a22ca6..73760d36b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2021_12_17_115802) do
+ActiveRecord::Schema.define(version: 2021_12_20_085352) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
@@ -360,6 +360,16 @@ ActiveRecord::Schema.define(version: 2021_12_17_115802) do
     t.index ["priority", "run_at"], name: "delayed_jobs_priority"
   end
 
+  create_table "education_program_members", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+    t.string "role"
+    t.uuid "member_id", null: false
+    t.uuid "program_id", null: false
+    t.datetime "created_at", precision: 6, null: false
+    t.datetime "updated_at", precision: 6, null: false
+    t.index ["member_id"], name: "index_education_program_members_on_member_id"
+    t.index ["program_id"], name: "index_education_program_members_on_program_id"
+  end
+
   create_table "education_programs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
     t.uuid "university_id", null: false
     t.string "name"
@@ -571,6 +581,8 @@ ActiveRecord::Schema.define(version: 2021_12_17_115802) do
   add_foreign_key "communication_website_posts", "communication_websites"
   add_foreign_key "communication_website_posts", "universities"
   add_foreign_key "communication_websites", "universities"
+  add_foreign_key "education_program_members", "administration_members", column: "member_id"
+  add_foreign_key "education_program_members", "education_programs", column: "program_id"
   add_foreign_key "education_programs", "education_programs", column: "parent_id"
   add_foreign_key "education_programs", "universities"
   add_foreign_key "education_programs_teachers", "administration_members", column: "education_teacher_id"
diff --git a/test/fixtures/education/program/members.yml b/test/fixtures/education/program/members.yml
new file mode 100644
index 000000000..d9c701b91
--- /dev/null
+++ b/test/fixtures/education/program/members.yml
@@ -0,0 +1,32 @@
+# == Schema Information
+#
+# Table name: education_program_members
+#
+#  id         :uuid             not null, primary key
+#  role       :string
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  member_id  :uuid             not null
+#  program_id :uuid             not null
+#
+# Indexes
+#
+#  index_education_program_members_on_member_id   (member_id)
+#  index_education_program_members_on_program_id  (program_id)
+#
+# Foreign Keys
+#
+#  fk_rails_...  (member_id => administration_members.id)
+#  fk_rails_...  (program_id => education_programs.id)
+#
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+  role: MyString
+  member: one
+  program: one
+
+two:
+  role: MyString
+  member: two
+  program: two
diff --git a/test/models/education/program/member_test.rb b/test/models/education/program/member_test.rb
new file mode 100644
index 000000000..3e004507a
--- /dev/null
+++ b/test/models/education/program/member_test.rb
@@ -0,0 +1,28 @@
+# == Schema Information
+#
+# Table name: education_program_members
+#
+#  id         :uuid             not null, primary key
+#  role       :string
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#  member_id  :uuid             not null
+#  program_id :uuid             not null
+#
+# Indexes
+#
+#  index_education_program_members_on_member_id   (member_id)
+#  index_education_program_members_on_program_id  (program_id)
+#
+# Foreign Keys
+#
+#  fk_rails_...  (member_id => administration_members.id)
+#  fk_rails_...  (program_id => education_programs.id)
+#
+require "test_helper"
+
+class Education::Program::MemberTest < ActiveSupport::TestCase
+  # test "the truth" do
+  #   assert true
+  # end
+end
-- 
GitLab