ruby-on-railshas-many-throughcollection-select

several has_many through instances, of diffrent categories, in the same form


I've been working on a simple scenario : users can join one group of each type. I am tring to build a form that will show all types, and under each type's name- the chosen group for that type, or a select box to choose a group of that type, if the user is not a member of one.

So far, I only could come up with a seperate form for each type - rather unconvinient. I'v Been tring to solve this for several days. I found explanations for uniqness of instances, collection_select and has_many through forms but I can't find a way to a combine solution.

Models:

class User < ActiveRecord::Base
  has_many :memberships
  has_many : groups, through: :memberships
end

class Group < ActiveRecord::Base
  has_many : memberships
  has_many :users, through: :memberships
  belongs_to :group_type
end

class Membership < ActiveRecord::Base
  belongs_to :user
  belongs_to :group
  validates :user_id, uniqueness: { scope: [:group_id, :group_type] }
end

class GroupType < ActiveRecord::Base
  has_many :groups
end

View:

<% @types = GroupTypes.all %>
<% @types.each do |type| %>
  <%= '#{@type.name}' %>
  <% @active_group = user.groups.where(type :type) %>
  <% if @active_group.exist? %>
      <%= '#{@active_group}' %>
       <%= link_to 'Leave', [group.user], method: :delete,  data: { confirm: 'Are you sure?' } %>
  <% else %>
      <%= form_for (Membership.new) do |f| %>
         <%= f.hidden_field :user_id, value: @user.id %>
         <%=  f.collection_select :group_id, Groups.all.where(type :type), :id, :name
         <%= f.submit %>
      <%end>  
    <%end> 
 <%end>        

controlller:

Class MembershipController < ApplicationController
def create
    @user = User.find(params[:user_id])
    @group = Group.find(params[:group_id])
    @membership = user.membership.create(group :@group )
    @user. memberships << membership 
    redirect_to user_register_path
  end

  def destroy
    @user = User.find(params[:user_id])
    @user.groups.find_by(group : params[:group_id]).delete
    redirect_to user_register_path
  end

  private
    def membership_params
      params.require(:membership).permit(:user_id, :group_id)
    end
end

Not sure if it is working properly, but as I wrote I am not happy with the idea of a form for each cathegory. was wondering if anyone could advise on a solution for that basic problem.

Thanks!


Solution

  • not a complete answer but I thought of posting

    the whole idea is by DRYING up your code you can easily see solution to your problems

    1) DROP the TypeGroup model

    class Group < ActiveRecord::Base
      has_many : memberships
      has_many :users, through: :memberships
      has_many :types, class_name: "Group",
                          foreign_key: "type_id"
      belongs_to :type, class_name: "Group"
    end
    

    migration

    class CreateTypes < ActiveRecord::Migration[5.0]
      def change
        create_table :groups do |t|
          t.references :type, index: true
          t.timestamps
        end
      end
    end
    

    2) your controller#new

    def new
        @active_groups = current_user.groups.map{ |group| group.types} 
        @types = Type.all
    end 
    

    3) use form helpers

    def user_group?
       type.group.user == current_user
    end
    

    4) DRY your form

    <% @types.each do |type| %>
      <%= '#{@type.name}' %>
      <% if  user_group? %>
          // show your form
      <%end>  
      // etc etc
    <%end> 
    

    also I never use this architecture, of showing the child form and using it to query for the parent, but usually I always start from the parent and build a nested form