lundi 11 janvier 2021

How to save multiple records at once with a uniqueness constraint in Rails?

This has been bugging me for days, I need some help.

I have a model Project with a has many relationship with the user (user has many projects). The projects are already predefined, and has a validates_uniqueness_of :roles, scope: :user_id, message: 'already exists' constraint defined in the model.

I currently have a form with checkboxes like so:

<h1>Create New Project</h1>
<%= form_for(@project) do |f| %>
  <%= f.label :user %>
  <%= f.select :user_id, @users %>
  <%= f.label :projects %>
  <ul>
    <% @user_projects.each do |project| %>
        <li>
          <%= f.check_box("projects",
            {  multiple: true },
            "#{project}", nil) 
          %>
          <%= "#{project}" %>
        </li>
    <% end %>
  </ul>
  <%= f.submit 'Create Projects' %>
<% end %>

<% if @project.errors.any? %>
  <ul id='form-error-list'>
    <% @project.errors.full_messages.each do |message| %>
      <li class='error-message'>
        <%= message %>
      </li>
    <% end %>
  </ul>
<% end %>

Where @users = User.all.collect { |user| [user.email, user.id] } and @user_projects = ['project 1', 'project 2', #...]

I'd like to save multiple projects for each users, and that by itself works fine. The only issue I have is that when I try to save a project that already exists in the database along with a project that doesn't, the page does not get rendered with an error message.

For example assume that user_id: 1, project: "project 1" exists in the database. If in the form, I check projects 1 and 2 with the same user id and try to save it, the instance with project 1 will not saved, but project 2 will. I'd like to make it so that if one or more validation failed, then the new page will be rendered with an error.

Here are my controller actions for Projects:

projects controller:

  def new
    @project = Project.new
    @users = User.all.collect { |user| [user.email, user.id] }
    @user_projects = ['project 1', 'project 2', #...]
  end

  def create
    @project = Project.new(project_params)
    @users = User.all.collect
    @user_projects = ['project 1', 'project 2', #...]

  if User.find_by(id: project_params['user_id']).projects.build(
    project_params['names'].map { |name| { names: name } }
  ).each(&:save)
    redirect_to projects_path, notice: 'Projects saved successfully'
  else
    render :new
  end
 end

 private

 def project_params
   params.require(:project).permit(:user_id, names : [])
 end

The only way I got it to work was implementing this into my create action:

build_multiple_projects = User.find_by(id: project_params['user_id']).projects.build(
    project_params['names'].map { |name| { names: name } }
  )
@errors = build_multiple_projects.map { |project|
  if Project.find_by({
    user_id: project.user_id,
    names: project.names
  }).present?
    project.save 
    project.errors.full_messages.each { |message| message }
  else
    nil
  end
}.uniq.compact

if @errors.present?
  render :new
else
  build_multiple_projects.each(&:save)
  redirect_to projects_path, notice: 'User projects have successfully been created'
end

But honestly I smell bad code in this, and don't want to use it.

Please provide me with tips! Thank you!




Aucun commentaire:

Enregistrer un commentaire