App is using a combination of Gems to create a multi-tenant application where account owners can invite users. All data shown is then scoped to the :account_id column based on the current user's :account_id value. It is using Devise for Accounts and Users. It is using Devise Invitable to allow accounts to send an invitation by email to Users to create their account and subsequently upon creation that user is assigned the :account_id of the Account id that invited them to join.
In development environment, the :account_id is being assign correctly upon InvitationsController#Create action. In production environment on Heroku it fails to save the :account_id to the User upon the same action.
Gemfile:
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.7.1'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3', '>= 6.0.3.2'
# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Gem for Users and Account control
gem 'devise'
# Gems for making things pretty
gem 'bootstrap'
gem 'font-awesome-rails'
# Use for creating graphs and grouping data
gem 'chartkick'
gem 'groupdate'
# Pagination gem
gem 'kaminari'
# Use Active Storage variant
# gem 'image_processing', '~> 1.2'
#Use to make mulitenancy simpler
gem 'acts_as_tenant'
#For Account owners to invite their company's Users
gem 'devise_invitable'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '~> 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
group :test do
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '>= 2.15'
gem 'selenium-webdriver'
# Easy installation and use of web drivers to run system tests with browsers
gem 'webdrivers'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Routes.rb
Rails.application.routes.draw do
devise_for :accounts, controllers: { sessions: 'accounts/sessions', registrations: 'accounts/registrations', invitations: 'users/invitations' }
devise_for :users, controllers: { sessions: 'users/sessions', invitations: 'users/invitations' }
resources :downtimes
resources :downtime_criteria
resources :actionitems do
resources :comments
end
resources :production_runs do
resources :production_hours
end
root 'welcome#index'
end
ApplicationController
class ApplicationController < ActionController::Base
set_current_tenant_through_filter
before_action :find_current_tenant
def find_current_tenant
current_account_id = user_signed_in? ? current_user.account : nil
set_current_tenant(current_account_id)
end
def authenticate_inviter!
authenticate_account!(force: true)
end
end
/users/InvitationsController
class Users::InvitationsController < Devise::InvitationsController
before_action :configure_permitted_parameters, if: :devise_controller?
def create
super
User.invite!(:email => params[:email], :account_id => current_account.id)
User.where(email: params[:user][:email]).update_all(account_id: current_account.id)
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:invite, keys: [:account_id])
end
end
Account.rb
class Account < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :users, dependent: :destroy
include DeviseInvitable::Inviter
end
User.rb
class User < ApplicationRecord
acts_as_tenant(:account)
belongs_to :account
validates :email, :account_id, presence: true
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
/users/invitations/_new.html.erb
<h2><%= t "devise.invitations.new.header" %></h2>
<%= form_for(resource, as: @invitation, url: invitation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "users/shared/error_messages", resource: resource %>
<% resource.class.invite_key_fields.each do |field| -%>
<div class="field">
<%= f.label field %><br />
<%= f.text_field field %>
</div>
<% end -%>
<div class="actions">
<%= f.submit t("devise.invitations.new.submit_button") %>
</div>
<% end %>
heroku logs --tail
2020-09-03T02:57:16.793404+00:00 heroku[router]: at=info method=POST path="/users/invitation" host=gentle-cliffs-24710.herokuapp.com request_id=cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8 fwd="108.166.134.134" dyno=web.1 connect=0ms service=646ms status=500 bytes=1827 protocol=https
2020-09-03T02:57:16.744528+00:00 app[web.1]: D, [2020-09-03T02:57:16.744380 #4] DEBUG -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."invitation_token" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["invitation_token", "f865f1a5e1e8e8f1576567e1f539ba6d9d3a1e85d5b21fa8e8fc72a0a7a1341d"], ["LIMIT", 1]]
2020-09-03T02:57:16.750905+00:00 app[web.1]: D, [2020-09-03T02:57:16.750774 #4] DEBUG -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] (3.2ms) BEGIN
2020-09-03T02:57:16.753036+00:00 app[web.1]: D, [2020-09-03T02:57:16.752895 #4] DEBUG -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] User Create (1.3ms) INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at", "invitation_token", "invitation_created_at", "invitation_sent_at", "invited_by_type", "invited_by_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id" [["email", "testing12345@gmail.com"], ["encrypted_password", "$2a$11$sPBv6kzgZNZcaDhRpNORcuThcEFQQh8xqTiIJ45izWX0v3eyBtgKK"], ["created_at", "2020-09-03 02:57:16.746102"], ["updated_at", "2020-09-03 02:57:16.746102"], ["invitation_token", "f865f1a5e1e8e8f1576567e1f539ba6d9d3a1e85d5b21fa8e8fc72a0a7a1341d"], ["invitation_created_at", "2020-09-03 02:57:16.744928"], ["invitation_sent_at", "2020-09-03 02:57:16.744928"], ["invited_by_type", "Account"], ["invited_by_id", 2]]
2020-09-03T02:57:16.758183+00:00 app[web.1]: D, [2020-09-03T02:57:16.758057 #4] DEBUG -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] (4.4ms) COMMIT
2020-09-03T02:57:16.765162+00:00 app[web.1]: I, [2020-09-03T02:57:16.765033 #4] INFO -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Rendering users/mailer/invitation_instructions.html.erb
2020-09-03T02:57:16.767966+00:00 app[web.1]: I, [2020-09-03T02:57:16.767824 #4] INFO -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Rendered users/mailer/invitation_instructions.html.erb (Duration: 2.6ms | Allocations: 517)
2020-09-03T02:57:16.771219+00:00 app[web.1]: I, [2020-09-03T02:57:16.771101 #4] INFO -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Rendering users/mailer/invitation_instructions.text.erb
2020-09-03T02:57:16.773629+00:00 app[web.1]: I, [2020-09-03T02:57:16.773519 #4] INFO -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Rendered users/mailer/invitation_instructions.text.erb (Duration: 2.2ms | Allocations: 393)
2020-09-03T02:57:16.777350+00:00 app[web.1]: D, [2020-09-03T02:57:16.777199 #4] DEBUG -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Devise::Mailer#invitation_instructions: processed outbound mail in 18.2ms
2020-09-03T02:57:16.788770+00:00 app[web.1]: I, [2020-09-03T02:57:16.788627 #4] INFO -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Delivered mail 5f505b8cbe3f1_493944479@739a533a-af2a-474c-a8c4-7bacfd76c8ca.mail (11.2ms)
2020-09-03T02:57:16.788846+00:00 app[web.1]: D, [2020-09-03T02:57:16.788754 #4] DEBUG -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Date: Thu, 03 Sep 2020 02:57:16 +0000
2020-09-03T02:57:16.788847+00:00 app[web.1]: From: please-change-me-at-config-initializers-devise@example.com
2020-09-03T02:57:16.788847+00:00 app[web.1]: Reply-To: please-change-me-at-config-initializers-devise@example.com
2020-09-03T02:57:16.788848+00:00 app[web.1]: To: testing12345@gmail.com
2020-09-03T02:57:16.788849+00:00 app[web.1]: Message-ID: <5f505b8cbe3f1_493944479@739a533a-af2a-474c-a8c4-7bacfd76c8ca.mail>
2020-09-03T02:57:16.788850+00:00 app[web.1]: Subject: Invitation instructions
2020-09-03T02:57:16.788850+00:00 app[web.1]: Mime-Version: 1.0
2020-09-03T02:57:16.788851+00:00 app[web.1]: Content-Type: multipart/alternative;
2020-09-03T02:57:16.788851+00:00 app[web.1]: boundary="--==_mimepart_5f505b8cbd144_493944334";
2020-09-03T02:57:16.788852+00:00 app[web.1]: charset=UTF-8
2020-09-03T02:57:16.788852+00:00 app[web.1]: Content-Transfer-Encoding: 7bit
2020-09-03T02:57:16.788853+00:00 app[web.1]:
2020-09-03T02:57:16.788853+00:00 app[web.1]:
2020-09-03T02:57:16.788853+00:00 app[web.1]: ----==_mimepart_5f505b8cbd144_493944334
2020-09-03T02:57:16.788854+00:00 app[web.1]: Content-Type: text/plain;
2020-09-03T02:57:16.788854+00:00 app[web.1]: charset=UTF-8
2020-09-03T02:57:16.788854+00:00 app[web.1]: Content-Transfer-Encoding: 7bit
2020-09-03T02:57:16.788855+00:00 app[web.1]:
2020-09-03T02:57:16.788855+00:00 app[web.1]: Hello testing12345@gmail.com
2020-09-03T02:57:16.788855+00:00 app[web.1]:
2020-09-03T02:57:16.788856+00:00 app[web.1]: Someone has invited you to https://gentle-cliffs-24710.herokuapp.com/:3000/, you can accept it through the link below.
2020-09-03T02:57:16.788856+00:00 app[web.1]:
2020-09-03T02:57:16.788857+00:00 app[web.1]: https://gentle-cliffs-24710.herokuapp.com/:3000/users/invitation/accept?invitation_token=yGi2R5_2acRXY3XJXSVN
2020-09-03T02:57:16.788857+00:00 app[web.1]:
2020-09-03T02:57:16.788857+00:00 app[web.1]:
2020-09-03T02:57:16.788858+00:00 app[web.1]: If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password.
2020-09-03T02:57:16.788858+00:00 app[web.1]:
2020-09-03T02:57:16.788858+00:00 app[web.1]: ----==_mimepart_5f505b8cbd144_493944334
2020-09-03T02:57:16.788859+00:00 app[web.1]: Content-Type: text/html;
2020-09-03T02:57:16.788859+00:00 app[web.1]: charset=UTF-8
2020-09-03T02:57:16.788859+00:00 app[web.1]: Content-Transfer-Encoding: 7bit
2020-09-03T02:57:16.788860+00:00 app[web.1]:
2020-09-03T02:57:16.788860+00:00 app[web.1]: <p>Hello testing12345@gmail.com</p>
2020-09-03T02:57:16.788861+00:00 app[web.1]:
2020-09-03T02:57:16.788861+00:00 app[web.1]: <p>Someone has invited you to https://gentle-cliffs-24710.herokuapp.com/:3000/, you can accept it through the link below.</p>
2020-09-03T02:57:16.788861+00:00 app[web.1]:
2020-09-03T02:57:16.788862+00:00 app[web.1]: <p><a href="https://gentle-cliffs-24710.herokuapp.com/:3000/users/invitation/accept?invitation_token=yGi2R5_2acRXY3XJXSVN">Accept invitation</a></p>
2020-09-03T02:57:16.788862+00:00 app[web.1]:
2020-09-03T02:57:16.788863+00:00 app[web.1]:
2020-09-03T02:57:16.788863+00:00 app[web.1]: <p>If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password.</p>
2020-09-03T02:57:16.788864+00:00 app[web.1]:
2020-09-03T02:57:16.788864+00:00 app[web.1]: ----==_mimepart_5f505b8cbd144_493944334--
2020-09-03T02:57:16.788865+00:00 app[web.1]:
2020-09-03T02:57:16.789171+00:00 app[web.1]: I, [2020-09-03T02:57:16.789103 #4] INFO -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Completed 500 Internal Server Error in 586ms (ActiveRecord: 21.3ms | Allocations: 11597)
2020-09-03T02:57:16.790582+00:00 app[web.1]: F, [2020-09-03T02:57:16.790480 #4] FATAL -- : [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8]
2020-09-03T02:57:16.790584+00:00 app[web.1]: [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] Errno::ECONNREFUSED (Connection refused - connect(2) for "localhost" port 25):
2020-09-03T02:57:16.790584+00:00 app[web.1]: [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8]
2020-09-03T02:57:16.790585+00:00 app[web.1]: [cdfe6b7c-7473-47d9-9ff1-ebca721ba0e8] app/controllers/users/invitations_controller.rb:5:in `create'
heroku run rails c
irb(main):006:0> User.last
D, [2020-09-03T02:58:48.541138 #4] DEBUG -- : User Load (1.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 8, email: "testing12345@gmail.com", created_at: "2020-09-03 02:57:16", updated_at: "2020-09-03 02:57:16", ***account_id: nil***>
irb(main):007:0>
I've tried running heroku restart. Out of ideas... anyone have some insight? Thanks