Подтвердить что ты не робот

Несколько пользовательских моделей с Ruby On Rails и разработайте отдельные маршруты регистрации, но один общий логин

Во-первых, я интенсивно искал Google и Yahoo, и я нашел несколько ответов на такие темы, как мои, но все они не покрывают то, что мне нужно знать.

У меня есть несколько моделей пользователей в моем приложении, теперь это клиенты, дизайнеры, розничные продавцы, и кажется, что еще впереди. Все они имеют разные данные, хранящиеся в их таблицах, и несколько областей на сайте, которым им разрешено или нет. Поэтому я решил пойти на разработку + 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'

В настоящее время контроллер регистрации остается стандартным (это "разработка/регистрация" ), но я подумал, что, поскольку у меня есть разные данные для хранения в разных моделях, мне тоже придется настраивать это поведение!?

Но с этой настройкой я получил помощников, таких как customer_signed_in? и designer_signed_in?, но мне действительно нужен общий помощник, например user_signed_in? для областей на сайте, доступных для всех пользователей, независимо от того, тип пользователя.

Мне также нужен помощник маршрутов, например new_user_session_path, а не несколько new_*type*_session_path и так далее. На самом деле все, что мне нужно, - это процесс регистрации...

Так что мне было интересно, ЕСЛИ ЭТО ПУТЬ ИДЕТ ДЛЯ этой проблемы??? Или есть лучшее/более легкое/менее необходимое решение для этого?

Спасибо заранее,
Роберт

4b9b3361

Ответ 1

Хорошо, поэтому я проработал и пришел к следующему решению.
Мне нужно было немного придумать немного, но это не так сложно.

Модель пользователя

# 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 имеет простую полиморфную ассоциацию, определяющую, является ли она клиентом или дизайнером.
Следующее, что мне нужно было сделать, - создать представления разработки с 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_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 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

UPDATE:

Чтобы иметь возможность поддерживать такие маршруты, как /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' }

Любой параметр, который не используется в синтаксисе маршрутов, внутренне передается в hash параметра. Поэтому :user передается в hash параметров.

Итак... что это. С небольшим tweeking здесь и там я получил его работу в довольно общем виде, который легко расширяется, когда многие другие модели пользователей используют общую таблицу пользователей.

Надеюсь, что кто-то сочтет это полезным.

Ответ 2

Мне не удалось найти какой-либо способ комментирования принятого ответа, поэтому я просто напишу здесь.

Есть несколько вещей, которые не работают точно так же, как утверждается принятый ответ, возможно, потому, что он устарел.

Во всяком случае, некоторые из вещей, которые я должен был решить сам:

  • Для UserRegistrationsController render_with_scope больше не существует, просто используйте render :new
  • Первая строка в функции create, снова в 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

код >

Ответ 3

Я следил за приведенными выше инструкциями и обнаружил некоторые пробелы и что инструкции были просто устаревшими, когда я его реализовывал.

Итак, после борьбы с ним весь день, позвольте мне поделиться с вами тем, что сработало для меня, и, надеюсь, это сэкономит вам несколько часов пота и слез.

  • Прежде всего, если вы не знакомы с полиморфизмом RoR, перейдите к этому руководству: http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/ После этого у вас будут установлены модели пользователей и пользователей, и вы сможете начать работать.

  • После этого, пожалуйста, следуйте большому учебнику Vapire для создания представлений со всеми partails.

  • То, что мне показалось наиболее расстраивающим, было то, что dut для использования последней версии 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 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.'
    

Надеюсь, что спасут вас, ребята, несколько часов.