Search code examples
ruby-on-railsvue.jsdevise-invitabledevise-token-auth

Rails 5 API + Vue.js frontend: form to invite users with devise_invitable


I am writing an invitation form with Vue.js. The invites should POST to a Rails 5 API. I am using devise_invitable for the invitation logic and email dispatch. However, I am having a problem with intercepting the create method, as I will be sending the invitation to multiple users so I want to perform a User.invite! for each user in the params.

My invite.vue file, which contains the form to post:

<template>
  <b-row>
    <b-col>
      Invite {{form.length}} members

      <b-form @submit.prevent="submitStaffInvite" id='staffInvite'>
        <div v-for="(row, idx) in form">
          <b-row>
            <b-col cols="3">
              <b-form-group id="firstNameGroup" label="First name">
                <b-form-input id="firstNameInput" name="user[first_name][]" type="text" v-model="row.firstName" autofocus></b-form-input>
              </b-form-group>
            </b-col>
            <b-col cols="3">
              <b-form-group id="lastNameGroup" label="Last name">
                <b-form-input id="lastNameInput" name="user[last_name][]" type="text" v-model="row.lastName"></b-form-input>
              </b-form-group>
            </b-col>
            <b-col cols="3">
              <b-form-group id="emailGroup" label="Staff email">
                <b-form-input id="emailInput" name="user[email][]" type="text" v-model="row.email"></b-form-input>
              </b-form-group>
            </b-col>
            <b-col cols="3">
              <b-button @click='removeRow(idx)'>Remove invitation</b-button>
            </b-col>
          </b-row>
        </div>
        <br />
        <b-button-group>
          <b-button @click.prevent='addRow'>Add invitation</b-button>
        </b-button-group>
        <br />
        <b-button-group>
          <b-button type="submit" variant="primary">Submit</b-button>
        </b-button-group>

      </b-form>
    </b-col>
  </b-row>
</template>

<script>
  export default {
    data: () => {
      return {
        form: [
          {
            email: '',
            firstName: '',
            lastName: ''
          }
        ]
      }
    },
    methods: {
      addRow: function () {
        this.form.push({
          email: '',
          firstName: '',
          lastName: ''
        })
      },
      removeRow: function (idx) {
        this.form.splice(idx, 1)
      },
      submitStaffInvite: function () {
        this.$axios.post('http://localhost:3001/auth/invitation', this.form)
          .then((res) => {
            if (res.status === 200) {
              this.$notify({
                text: res.data.message,
                group: 'alerts',
                type: 'success'
              })
            }
          })
          .catch(function (error) {
            error.response.data.errors.forEach((err) => {
              this.$notify({
                text: err,
                group: 'alerts',
                type: 'warning'
              })
            })
          })
      }
    }
  }
</script>

my users/invitations_controller.rb

class Users::InvitationsController < Devise::InvitationsController
  before_action :configure_permitted_parameters

  def create
    # TODO
    # Send email to each user in the form.
  end

  def edit
    sign_out send("current_#{resource_name}") if send("#{resource_name}_signed_in?")
    set_minimum_password_length
    resource.invitation_token = params[:invitation_token]
    redirect_to "http://localhost:3001/auth/invitation/accept?invitation_token=#{params[:invitation_token]}"
  end

  def update
    super do |resource|
      if resource.errors.empty?
        render json: { status: "Invitation Accepted!" }, status: 200
      else
        render json: resource.errors, status: 401
      end
    end
  end

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:accept_invitation, keys: [:first_name, :last_name])
  end
end

my routes.rb

Rails.application.routes.draw do
  # Using devise_token_auth for the API
  mount_devise_token_auth_for 'User', at: 'auth',
                                      defaults: { format: :json },
                                      controllers: {
                                        invitations: 'users/invitations'
                                      }
  ...
end

I can see that the following routes exist:

PUT      /auth/invitation(.:format)            devise/invitations#update
POST     /auth/invitation(.:format)            devise/invitations#create

The problem I can see here though is that I'd expect the routes to look like this:

PUT      /auth/invitation(.:format)            users/invitations#update
POST     /auth/invitation(.:format)            users/invitations#create

This behavior might be caused by the devise_token_auth gem, which I'm not sure how to correct.

Again, when I submit this form I'd expect to be able to intercept the create method, so to then call User.invite! on all the users that are listed in the form. I've tried adding a byebug or binding.pry inside the create method but the code doesn't appear to execute, which means it's being bypassed.

The error message I see is:

Processing by Devise::InvitationsController#create as HTML
  Parameters: {"_json"=>[{"email"=>"[email protected]", "firstName"=>"Test", "lastName"=>"Last"}], "invitation"=>{"_json"=>[{"email"=>"[email protected]", "firstName"=>"Test", "lastName"=>"Last"}]}}
Completed 500 Internal Server Error in 0ms (ActiveRecord: 0.0ms)



ArgumentError (wrong number of arguments (given 1, expected 0)):

Am I not supposed to pass the form data to the devise_invitable's create method? Any help is much appreciated.

Thanks in advance!


Solution

  • Try changing your routes to

    mount_devise_token_auth_for 'User', at: 'auth', skip: [:invitations]
    
    devise_for :users, path: "auth", only: [:invitations],
    controllers: { invitations: 'users/invitations' }
    

    For more information you can refer to this article