简体   繁体   中英

Adding an array of user id's to a model - Rails 4

I'm trying to make an app for students to post projects. When they create a post, they can add their team mates. Each student has a user id, and I want the student creating the project to be able to select other ids from the same organisation as their team mates. The model associations are:

user

has_and belongs_to_many :projects

project

has_and belongs_to_many :users

I have a project model, with:

:user_id (integer)
:team_mates (integer)

In my projects form, I want the student (creating the project, to select other ids (from a list of students who belong to the same organisation) as team mates. My first question is whether the team mates attribute should be an integer (since there might be more than one team mate, in which case, can this attribute hold an array?

My next problem is - I'm lost for how to go about this. If I add a select line to my project form, to add user_ids, where the user.organisation equals the current user's id, then the student creating the project should be able to see a list of possible options.

Then in my projects show page, I want to display each student in the team.

Can anyone help with how to approach this? I'm lost and stuck for where to find examples of similar problems.

UPDATE

I found this article: http://collectiveidea.com/blog/archives/2015/07/30/bi-directional-and-self-referential-associations-in-rails/

I'm confused though. I don't know whether I should join projects with users (through a join table I've called teams) or whether I should join users with users, through a join table called 'teams'.

If I join users with projects, it makes sense to me that the user who creates a project can choose other users to be project team mates. However, it isn't true to say that each project has many teams (which is what this example shows). I'm not sure about changing the has_many to a has_one, since the article goes on to explain about the has_many through join.

If i join users to users, then a user with many projects may have different teams for each project. So that wouldn't be correct.

Taking the article as an example, I tried:

create teams model:

class CreateTeams < ActiveRecord::Migration
  def change
    create_table :teams do |t|

      t.references :project, index: true, foreign_key: true
      t.references :team_mate, index: true


      t.timestamps null: false
    end
    add_foreign_key :teams, :projects, column: :team_mate_id
    add_index :teams, [:project_id, :team_mate_id], unique: true
  end
end

Team.rb

belongs_to :project
belongs_to :team_mate, class_name: "Profile"

Teams controller (I will figure this out later - its commented for now since I don't have a matchmaker section yet):

class TeamsController < ApplicationController

before_action :resync_matches, only: :index

def index
  # several orders of magnitude faster
  @team_mates = current_user.team_mates
                               .page(params[:page])
end

private

def resync_matches
  # only resync if we have to
  if current_user.teams_outdated?
    new_matches = MatchMaker.matches_for(current_user)
    current_user.team_mates.replace(new_matches)
  end
end

end

project.rb

has_one :team
has_many :team_mates, through: :teams, dependent: :destroy

I changed this so that projects have one team rather than many.

Im confused about this and not sure how to get this up and running. In my projects form, I want to offer users (who create projects) to pick profiles of other users who are team mates. I'm lost at this point.

I tried to make a Teams Helper:

module TeamsHelper
  def team_mate_options
    s = ''
    Profile.in_same_organisation.each do |profile|
      s << "<option value='#{profile.id}'>#{profile.user.full_name}</option>"
    end
    s.html_safe
  end



end

In my profile.rb, I tried to make a scope to get the profiles who belong to the same organisation as the project creator (although I'm not sure this is correct):

scope :in_same_organisation, -> (organisation_id) { where(organisation_id: organisation_id) }

Then in my projects form I tried to add a select option:

<div class="form-group">
                        <%= label_tag 'team_mates', 'Choose team mates' %>
                        <%= select_tag 'team_mates', team_mate_options, multiple: true, class: 'form-control chosen-it' %>
                    </div>

VISHAL'S SUGGESTION

Taking Vishal's suggestion, I have implemented the structure proposed. I'm having a problem with the projects form. My complete setup is:

Models Organisation

has_many :profiles

Profile

  has_many :projects
  belongs_to :organisation
  has_many :teams, foreign_key: "team_mate_id"
  has_many :team_projects, through: :teams, source: :project

Project

belongs_to :profile
has_many :teams
has_many :team_mates, through: :teams

Team

belongs_to :project
belongs_to :team_mate, class_name: "Profile"

My teams table has:

create_table "teams", force: :cascade do |t|
    t.integer  "project_id"
    t.integer  "team_mate_id"
    t.datetime "created_at",   null: false
    t.datetime "updated_at",   null: false
  end

Then in my project form, I have:

<%= f.label :team_mates, :label => "Add a team member" %>
<%= f.collection_select(:team_mate_id, Profile.all, :id, :team_mate_select, {prompt: "Select the team member"}, {:required => true}) %>

In my profile model, I have:

def team_mate_select
    self.user.formal_name
end

My structure is that profiles belong to users. In user, I have method called formal name which adds a title to the users name.

When I save this and try it, I get an error that says:

undefined method `team_mate_id' for #<Project:0x007fa08ed3d8e0>

(highlighting the collection select line of the project form)

My projects/form.html.erb has:

<%= simple_form_for(@project) do |f| %>
            <%= f.error_notification %>

                <div class="form-inputs">

                    <%= f.input :title, :label => "Title", autofocus: true %>
                    <%= f.input :description, :as => :text, :label => "Describe your project", :input_html => {:rows => 10} %>
                    <%= f.input :remark, :as => :text, :label => "Is there an interesting fact or statistic that's relevant to this research?", :input_html => {:rows => 5}, :placeholder => "In fact, ...(insert a fact which shows why this research might be interesting or relevant)" %>

                    <%= f.input :hero_image, :label => "Add an image" %>

                        <%= f.label :team_mates, :label => "Add a team member" %>
                        <%= f.collection_select(:team_id, Profile.all, :id, :team_mate_select, {prompt: "Select the team member"}, {:required => true}) %>


                <div class="form-actions" style="margin-top:50px">
                    <%= f.button :submit, "Create", :class => 'formsubmit' %>
                </div>
        <% end %>

Introduce a Team model, with has_and belongs_to_many relations between a User model and a team model. Then, a Team represents an array of User objects.

What you are looking for is self-referential associations . You would have to create a join table that keeps the records of which user is team member of which user; something like following:

+-----------+----------------+------------+-------------+
|  user_id  | team_member_id | created_at | updated_at  |
+-----------|----------------+------------+-------------+
|           |                |            |             |
+-----------+----------------+------------+-------------+

You can think on the lines of 'what resource you are creating'. When you add a new project, you are creating a Project resource. When the project's creator adds someone else to the project, you are creating a ProjectMemberRelationship resource. So, I think you can get what you need with only these models - Organisation, User, Project, ProjectMemberRelationship (and a table for each of them).

You will need a projects table that has a reference to users through user_id field.

Create ProjectMemberRelationship model with

rails generate model ProjectMemberRelationship project_id:integer member_id:integer

Organisation Model:

has_many :users

User model:

belongs_to :organization
has_many :projects
has_many :project_member_relationships, foreign_key: "member_id"
has_many: collaborated_projects, through: :project_member_relationships, source: :project

You need foreign_key: "member_id" as the column name is not user_id as expected by Rails by default. And you need source: :project because the column name is not collaborated_project_id but project_id.

Project model:

belongs_to :user
has_many :project_member_relationships
has_many :members, through: :project_member_relationships

ProjectMemberRelationship model:

belongs_to :project
belongs_to :member, class_name: "User"

With models defined as above, every time a member is added to a project, a new row is added to project_member_relationships table that stores the project_id for the project and member_id of the user that was added, while users & projects tables are unaffected by this action.

Let's divide the task in 2 stops.

Step 1: A user creates a project.

Step 2: The user adds collaborators to the project.

Step 1 is fairly easy and straightforward. You create a project with, say its topic and user_id of the user who created it.

Now on the project's show page, you can have a form to add a new project_member_relationship. You only need 2 fields to submit here - member_id (for the user to be added) & project_id (which equals params[:id] on projects#show page). For member_id field, you can use collection_select where your collection is the users that belong to the same organisation as the creator, as required. Mind you, one submission of this form creates only one project member at a time.

I haven't tested the code yet but I'll be happy to help if you run into issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM