Многопользовательские модели с Ruby On Rails и предусматривают иметь отдельные маршруты регистрации, но один общий маршрут входа
Во-первых, я интенсивно искал с Google и Yahoo, и я нашел несколько ответов на такие темы, как моя, но все они на самом деле не охватывают то, что мне нужно знать.
У меня есть несколько пользовательских моделей в моем приложении, на данный момент это покупатели, дизайнеры, розничные продавцы, и кажется, что это еще не все. Все они имеют разные данные, хранящиеся в своих таблицах и в нескольких областях сайта, на которые им разрешено или нет. Поэтому я решил пойти по пути devise+CanCan и попытать счастья с полиморфными ассоциациями, поэтому я настроил следующие модели:
class User < AR
belongs_to :loginable, :polymorphic => true
end
class Customer < AR
has_one :user, :as => :loginable
end
class Designer < AR
has_one :user, :as => :loginable
end
class Retailer < AR
has_one :user, :as => :loginable
end
Для регистрации у меня есть настроенные виды для каждого типа пользователя, и мои маршруты настроены так:
devise_for :customers, :class_name => 'User'
devise_for :designers, :class_name => 'User'
devise_for :retailers, :class_name => 'User'
На данный момент контроллер регистрации оставлен в качестве стандартного (то есть "devise/registrations"), но я решил, что поскольку у меня есть разные данные для хранения в разных моделях, мне также придется настроить это поведение!?
Но с этой настройкой я получил помощников, таких как customer_signed_in?
а также designer_signed_in?
, но мне действительно нужен такой помощник, как user_signed_in?
для областей на сайте, которые доступны для всех пользователей, независимо от типа пользователя.
Я также хотел бы, как помощник маршрутов, как new_user_session_path
вместо нескольких new_*type*_session_path
и так далее. На самом деле все, что мне нужно, - это процесс регистрации...
Так что мне было интересно, если это путь для решения этой проблемы??? Или есть лучшее / более легкое / менее индивидуальное решение для этого???
Заранее спасибо,
Роберт
3 ответа
Итак, я проработал это и пришел к следующему решению.
Мне нужно было немного поучаствовать в разработке, но это не так сложно.
Модель пользователя
# user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
belongs_to :rolable, :polymorphic => true
end
Модель клиента
# customer.rb
class Customer < ActiveRecord::Base
has_one :user, :as => :rolable
end
Модель Дизайнер
# designer.rb
class Designer < ActiveRecord::Base
has_one :user, :as => :rolable
end
Таким образом, модель User имеет простую полиморфную ассоциацию, определяющую, является ли она Customer или Designer.
Следующее, что мне нужно было сделать, - это сгенерировать вид rails g devise:views
быть частью моего заявления. Поскольку мне нужно было только настроить регистрацию, я сохранил app/views/devise/registrations
только папку и удалил остальное.
Затем я настроил вид регистрации для новых регистраций, которые можно найти в app/views/devise/registrations/new.html.erb
после того, как вы их сгенерировали.
<h2>Sign up</h2>
<%
# customized code begin
params[:user][:user_type] ||= 'customer'
if ["customer", "designer"].include? params[:user][:user_type].downcase
child_class_name = params[:user][:user_type].downcase.camelize
user_type = params[:user][:user_type].downcase
else
child_class_name = "Customer"
user_type = "customer"
end
resource.rolable = child_class_name.constantize.new if resource.rolable.nil?
# customized code end
%>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= my_devise_error_messages! # customized code %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<% # customized code begin %>
<%= fields_for resource.rolable do |rf| %>
<% render :partial => "#{child_class_name.underscore}_fields", :locals => { :f => rf } %>
<% end %>
<%= hidden_field :user, :user_type, :value => user_type %>
<% # customized code end %>
<div><%= f.submit "Sign up" %></div>
<% end %>
<%= render :partial => "devise/shared/links" %>
Для каждого типа пользователя я создал отдельный партиал с настраиваемыми полями для этого конкретного типа пользователя, т.е. Designer -> _designer_fields.html
<div><%= f.label :label_name %><br />
<%= f.text_field :label_name %></div>
Затем я настраиваю маршруты для устройства, чтобы использовать пользовательский контроллер при регистрации
devise_for :users, :controllers => { :registrations => 'UserRegistrations' }
Затем я сгенерировал контроллер для обработки настроенного процесса регистрации, скопировал исходный код из create
метод в Devise::RegistrationsController
и изменил его так, чтобы он работал по-моему (не забудьте переместить файлы вида в соответствующую папку, в моем случае app/views/user_registrations
class UserRegistrationsController < Devise::RegistrationsController
def create
build_resource
# customized code begin
# crate a new child instance depending on the given user type
child_class = params[:user][:user_type].camelize.constantize
resource.rolable = child_class.new(params[child_class.to_s.underscore.to_sym])
# first check if child instance is valid
# cause if so and the parent instance is valid as well
# it's all being saved at once
valid = resource.valid?
valid = resource.rolable.valid? && valid
# customized code end
if valid && resource.save # customized code
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
else
set_flash_message :notice, :inactive_signed_up, :reason => inactive_reason(resource) if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords(resource)
respond_with_navigational(resource) { render_with_scope :new }
end
end
end
Все это в основном заключается в том, что контроллер определяет, какой тип пользователя должен быть создан в соответствии с user_type
параметр, который доставляется на контроллер create
метод скрытым полем в представлении, которое использует параметр простым GET-параметром в URL.
Например:
Если вы идете в /users/sign_up?user[user_type]=designer
Вы можете создать конструктор.
Если вы идете в /users/sign_up?user[user_type]=customer
Вы можете создать клиента.
my_devise_error_messages!
Метод является вспомогательным методом, который также обрабатывает ошибки валидации в ассоциативной модели, основанной на оригинальной devise_error_messages!
метод
module ApplicationHelper
def my_devise_error_messages!
return "" if resource.errors.empty? && resource.rolable.errors.empty?
messages = rolable_messages = ""
if !resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
end
if !resource.rolable.errors.empty?
rolable_messages = resource.rolable.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
end
messages = messages + rolable_messages
sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count + resource.rolable.errors.count,
:resource => resource.class.model_name.human.downcase)
html = <<-HTML
<div id="error_explanation">
<h2>#{sentence}</h2>
<ul>#{messages}</ul>
</div>
HTML
html.html_safe
end
end
ОБНОВИТЬ:
Чтобы иметь возможность поддерживать такие маршруты, как /designer/sign_up
а также /customer/sign_up
Вы можете сделать следующее в вашем файле маршрутов:
# routes.rb
match 'designer/sign_up' => 'user_registrations#new', :user => { :user_type => 'designer' }
match 'customer/sign_up' => 'user_registrations#new', :user => { :user_type => 'customer' }
Любой параметр, который не используется в синтаксисе маршрутов внутри, передается в хэш params. Так :user
передается в хэш params.
Итак, это все. Немного подправив кое-что, я понял, что он работает достаточно общим образом, что легко расширяется многими другими моделями пользователей, использующими общую таблицу пользователей.
Надеюсь, кто-то найдет это полезным.
Мне не удалось найти способ прокомментировать принятый ответ, поэтому я просто напишу здесь.
Есть пара вещей, которые не работают точно так, как указано в принятом ответе, возможно потому, что он устарел.
Во всяком случае, некоторые вещи, которые я должен был решить сам:
- Для UserRegistrationsController,
render_with_scope
больше не существует, просто используйтеrender :new
Первая строка в функции создания, опять же в UserRegistrationsController, не работает, как указано. Просто попробуйте использовать
# Getting the user type that is send through a hidden field in the registration form. user_type = params[:user][:user_type] # Deleting the user_type from the params hash, won't work without this. params[:user].delete(:user_type) # Building the user, I assume. build_resource
вместо просто build_resource
, Некоторая ошибка массового назначения появлялась, когда не изменялась.
- Если вы хотите получить всю пользовательскую информацию в методе Devise current_user, внесите следующие изменения:
class ApplicationController < ActionController::Base
protect_from_forgery
# Overriding the Devise current_user method
alias_method :devise_current_user, :current_user
def current_user
# It will now return either a Company or a Customer, instead of the plain User.
super.rolable
end
end
Я следовал приведенным выше инструкциям и обнаружил некоторые пробелы, и что инструкции просто устарели, когда я их выполнял.
Поэтому, после целого дня борьбы, позвольте мне поделиться с вами тем, что сработало для меня, и, надеюсь, это сэкономит вам несколько часов пота и слез.
Прежде всего, если вы не знакомы с полиморфизмом RoR, ознакомьтесь с этим руководством: http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/ будут установлены разработанные и пользовательские модели пользователей, и вы сможете начать работать.
После этого, пожалуйста, следуйте великолепному руководству Vapire по созданию представлений со всеми параметрами.
Больше всего меня расстраивало то, что при использовании последней версии Devise (3.5.1) RegistrationController отказывался работать. Вот код, который заставит его снова работать:
def create meta_type = params[:user][:meta_type] meta_type_params = params[:user][meta_type] params[:user].delete(:meta_type) params[:user].delete(meta_type) build_resource(sign_up_params) child_class = meta_type.camelize.constantize child_class.new(params[child_class.to_s.underscore.to_sym]) resource.meta = child_class.new(meta_type_params) # first check if child intance is valid # cause if so and the parent instance is valid as well # it's all being saved at once valid = resource.valid? valid = resource.meta.valid? && valid # customized code end if valid && resource.save # customized code yield resource if block_given? if resource.persisted? if resource.active_for_authentication? set_flash_message :notice, :signed_up if is_flashing_format? sign_up(resource_name, resource) respond_with resource, location: after_sign_up_path_for(resource) else set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format? expire_data_after_sign_in! respond_with resource, location: after_inactive_sign_up_path_for(resource) end else clean_up_passwords resource set_minimum_password_length respond_with resource end end end
а также добавьте эти переопределения, чтобы перенаправления работали нормально:
protected def after_sign_up_path_for(resource) after_sign_in_path_for(resource) end def after_update_path_for(resource) case resource when :user, User resource.meta? ? another_path : root_path else super end end
Для того, чтобы разработанные флеш-сообщения продолжали работать, вам необходимо обновить
config/locales/devise.en.yml
вместо переопределенного RegistraionsControlloer от UserRegistraionsControlloer все, что вам нужно сделать, это добавить этот новый раздел:user_registrations: signed_up: 'Welcome! You have signed up successfully.'
Надеюсь, это спасет вас, ребята, несколько часов.