设计公寓在设计用户创建时或之后创建租户
Devise Apartment create tenant on or after devise user create
我有一个使用 devise 和 apartment gems 的小应用程序。
我有一个拥有一个组织 (tenant_name) 的用户 (devise)。
该组织有一个:所有者,class_name 'user'
我还想使用 Devise 注册表创建租户并分配给管理员。
我想我已经阅读了很多关于设计公寓/设计自定义控制器/设计嵌套属性的教程,但我自己也很困惑。
注册表单
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>
用户模型
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
组织模型
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
我知道我需要更改我的设计控制器上的创建方法,所以我使用 here
生成了设计自定义控制器
rails generate devise:controllers users
并添加了自定义消毒剂
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
架构
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
当我完成注册过程时,我得到了用户的未知属性 'organizations'。
更新:
我添加了两个新的迁移以添加对组织中用户的引用,以及对用户的组织。
我这样做是为了为所有者创建关联,我是否应该调用参考所有者
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
我的架构现在看起来像这样:
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
我按照指示将我的用户/注册控制器更改为:
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
现在我的表格
<%= 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 %>
我现在的问题是我在表单视图中看不到组织名称字段。
在has_one关系中你应该使用单数形式:
<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>
我认为您的其余代码应该可以正常工作!
编辑:如何实例化新组织
您指出表单是空的,那是因为没有组织实例。在您的用户控制器中执行以下操作:
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
我有一个使用 devise 和 apartment gems 的小应用程序。
我有一个拥有一个组织 (tenant_name) 的用户 (devise)。
该组织有一个:所有者,class_name 'user'
我还想使用 Devise 注册表创建租户并分配给管理员。 我想我已经阅读了很多关于设计公寓/设计自定义控制器/设计嵌套属性的教程,但我自己也很困惑。
注册表单 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>
用户模型
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
组织模型
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
我知道我需要更改我的设计控制器上的创建方法,所以我使用 here
生成了设计自定义控制器rails generate devise:controllers users
并添加了自定义消毒剂
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
架构
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
当我完成注册过程时,我得到了用户的未知属性 'organizations'。
更新:
我添加了两个新的迁移以添加对组织中用户的引用,以及对用户的组织。
我这样做是为了为所有者创建关联,我是否应该调用参考所有者
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
我的架构现在看起来像这样:
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
我按照指示将我的用户/注册控制器更改为:
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
现在我的表格
<%= 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 %>
我现在的问题是我在表单视图中看不到组织名称字段。
在has_one关系中你应该使用单数形式:
<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>
我认为您的其余代码应该可以正常工作!
编辑:如何实例化新组织
您指出表单是空的,那是因为没有组织实例。在您的用户控制器中执行以下操作:
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