2

I have a little app that is using devise and apartment gems.

I have User(devise) who has one organization(tenant_name).

The organization has one :owner, class_name 'user'

I want to use the Devise registration form to also create the tenant and assign to the admin. I think I have read that many tutorials on devise apartment / devise custom controllers / devise nested attributes that I have confused myself.

Registrations form app/views/devise/registrations/new.html.erb

<div class="row">
  <div class="col-lg-4 col-md-6 ml-auto mr-auto">
    <h1 class="text-center">Sign Up</h1>

    <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
      <%= render partial: 'devise/shared/error_messages', resource: resource %>

      <div class="form-group">
        <%= f.email_field :email, autofocus: false, class: 'form-control', placeholder: "Email Address" %>
      </div>
      <div class="form-group">
        <%= f.simple_fields_for :organizations do |o| %>
          <%= o.input :name, placeholder: "Organization Name", warning: "Cant Be Changed", label: false  %>
        <% end %>
      </div>

      <div class="form-group">
        <%= f.password_field :password, autocomplete: "off", class: 'form-control', placeholder: 'Password' %>
      </div>

      <div class="form-group">
        <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control', placeholder: 'Confirm Password' %>
      </div>

      <div class="form-group">
        <%= f.submit "Sign up", class: "btn btn-primary btn-block btn-lg" %>
      </div>
    <% end %>

    <div class="text-center">
      <%= render "devise/shared/links" %>
    </div>
  </div>
</div>

User Model

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_one :organization, dependent: :destroy
  after_create :init_organization
  accepts_nested_attributes_for :organization

  private

  def init_organization
    self.create_organization!
  end



end

Organization Model

class Organization < ApplicationRecord
    has_one :owner, class_name: 'User'
    has_many :organizations_users
    has_many :users, through: :organizations_users
    has_many :clients

    after_create :create_tenant

    def tenant_name
      "#{self.id}"
    end

    private

    def create_tenant
      Apartment::Tenant.create(self.tenant_name)
    end
end

I know that I need to alter my create method on my devise controller so i generated devise custom controllers with from here

rails generate devise:controllers users

and added the custom sanitizers

class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  # GET /resource/sign_up
  # def new
  #   super
  # end

  # POST /resource
  def create
    super
  end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
   devise_parameter_sanitizer.permit(:sign_up, keys: [:email, organizations: [:name]])
  end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end

schema

ActiveRecord::Schema.define(version: 2019_05_12_083957) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "pgcrypto"
  enable_extension "plpgsql"
  enable_extension "uuid-ossp"

  create_table "Organizations_Users", id: false, force: :cascade do |t|
    t.uuid "Organization_id", null: false
    t.uuid "User_id", null: false
  end

  create_table "clients", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.uuid "organization_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["organization_id"], name: "index_clients_on_organization_id"
  end

  create_table "equipment", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.uuid "site_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["site_id"], name: "index_equipment_on_site_id"
  end

  create_table "organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.uuid "user", null: false
  end

  create_table "sites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.uuid "client_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["client_id"], name: "index_sites_on_client_id"
  end

  create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "clients", "organizations"
  add_foreign_key "equipment", "sites"
  add_foreign_key "sites", "clients"
end

when i go through the sign up process i get unknown attribute 'organizations' for User.

Update:

I added two new migration to add reference to user on the organization, and organization to the user.

I did this to create the association for the owner, should i have called the reference owner

class AddUserToOrganization < ActiveRecord::Migration[5.2]
  def change
    add_reference :organizations, :user, type: :uuid, null: false, index: true, foreign_key: true
  end
end

class AddOrganizationToUser < ActiveRecord::Migration[5.2]
  def change
    add_reference :users, :organization, type: :uuid, null: false, index: true, foreign_key: true
  end
end

My Schema now looks like so:

ActiveRecord::Schema.define(version: 2019_05_13_223120) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "pgcrypto"
  enable_extension "plpgsql"
  enable_extension "uuid-ossp"

  create_table "Organizations_Users", id: false, force: :cascade do |t|
    t.uuid "Organization_id", null: false
    t.uuid "User_id", null: false
  end

  create_table "clients", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.uuid "organization_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["organization_id"], name: "index_clients_on_organization_id"
  end

  create_table "equipment", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.uuid "site_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["site_id"], name: "index_equipment_on_site_id"
  end

  create_table "organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.uuid "user_id", null: false
    t.index ["user_id"], name: "index_organizations_on_user_id"
  end

  create_table "sites", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.uuid "client_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["client_id"], name: "index_sites_on_client_id"
  end

  create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.uuid "organization_id", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["organization_id"], name: "index_users_on_organization_id"
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

  add_foreign_key "clients", "organizations"
  add_foreign_key "equipment", "sites"
  add_foreign_key "organizations", "users"
  add_foreign_key "sites", "clients"
  add_foreign_key "users", "organizations"
end

I followed the direction from to change my user/ registrations controller to :

class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  def new
    super
    @organization = Organization.new
  end

  # POST /resource
  def create
    super
  end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
   devise_parameter_sanitizer.permit(:sign_up, keys: [:email, organizations: [:name]])
  end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end

and my form now

<%= simple_form_for(resource, :as => resource_name, :url => session_path(resource_name), :html => {:class => 'form-horizontal' }) do |f| %>
      <%= render partial: 'devise/shared/error_messages', resource: resource %>

        <%= f.input :email, autofocus: false, class: 'form-control', placeholder: "Email Address", label: false  %>
        <%= f.simple_fields_for :organization do |o| %>
          <%= o.input :name, placeholder: "Organization Name", warning: "Cant Be Changed", label: false  %>
        <% end %>
        <%= f.input :password, autocomplete: "off", class: 'form-control', placeholder: 'Password', label: false  %>
        <%= f.input :password_confirmation, autocomplete: "off", class: 'form-control', placeholder: 'Confirm Password', label: false  %>
        <%= f.button :submit, "Sign up", class: "btn btn-primary btn-block btn-lg" %>
    <% end %>

my problem now is that i cannot see the organization name field in the view of the form.

Joe Bloggos
  • 889
  • 7
  • 24

1 Answers1

1

In has_one relation you should use the singular form:

  <div class="form-group">
    <%= f.simple_fields_for :organization do |o| %>
      <%= o.input :name, placeholder: "Organization Name", warning: "Cant Be Changed", label: false  %>
    <% end %>
  </div>

I think the rest of your code should work as is!

Edit: how to instantiate a new org

You indicated that the form is empty, that's because there is no org instance. Do the following in your user controller:

class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  GET /resource/sign_up
  def new
    super
    @user.organization = Organization.new
  end

  # POST /resource
  def create
    super
  end
end
bo-oz
  • 2,842
  • 2
  • 24
  • 44
  • when i change organizations to organization it is removed from my form... – Joe Bloggos May 13 '19 at 06:27
  • You should instantiate a new organization... otherwise you won't see one in your view. Either do it in the controller, or add the instantiation to the simple_fields call. See me edited answer. – bo-oz May 13 '19 at 07:08
  • thank you so much but now i get ActiveModel::MissingAttributeError in Users::RegistrationsController#new can't write unknown attribute `user_id` – Joe Bloggos May 13 '19 at 21:59
  • is this possibly because I am using UUID? – Joe Bloggos May 13 '19 at 22:10
  • Well, that's not really a problem... but you should have named it `user_id` according to rails conventions. You need to create a migration to change the name of the column. After that it should work. – bo-oz May 14 '19 at 05:23
  • But is it also changed in your database? Do you still get the error afterwards? – bo-oz May 14 '19 at 09:26
  • 1
    the schema is in the update? I dont know what you are referencing? I dont get an error cause I can't even see organization in the form, which has been the issue since i implemented the change. I think the issue is because I say the organization has_one :owner, class_name: 'User'... have i referenced that incorrectly in the schema? – Joe Bloggos May 14 '19 at 21:53
  • Yes the schema is wrong, your user foreign key in the organization table, should be named user_id, it is now user. That’s what I have been saying in previous comments. – bo-oz May 18 '19 at 14:27
  • Oh just read the update, sorry checking this on mobile... i don’t really get what problem you are having? Are you able to set the organization through a Rails console? ‘@user.organization = Organization.first’ – bo-oz May 18 '19 at 14:30