ruby-on-rails - tutorial - ruby on rails usos
Múltiples modelos de usuario con Ruby On Rails y diseñar rutas de registro separadas pero una ruta de inicio de sesión común (3)
De acuerdo, entonces lo resolví y obtuve la siguiente solución.
Necesitaba idear un poco de vestuario, pero no es tan complicado.
El modelo de usuario
# 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
El modelo del cliente
# customer.rb
class Customer < ActiveRecord::Base
has_one :user, :as => :rolable
end
El modelo de Diseñador
# designer.rb
class Designer < ActiveRecord::Base
has_one :user, :as => :rolable
end
Entonces, el modelo de Usuario tiene una asociación polimórfica simple que define si es un Cliente o un Diseñador.
Lo siguiente que tuve que hacer fue generar vistas de diseño con rails g devise:views
para ser parte de mi aplicación. Como solo necesitaba personalizar el registro, guardé la carpeta app/views/devise/registrations
únicamente y eliminé el resto.
Luego personalicé la vista de registros para nuevos registros, que se pueden encontrar en la app/views/devise/registrations/new.html.erb
después de generarlos.
<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" %>
Para cada tipo de Usuario, creé un parcial por separado con los campos personalizados para ese tipo de Usuario específico, es decir, Diseñador -> _designer_fields.html
<div><%= f.label :label_name %><br />
<%= f.text_field :label_name %></div>
Luego configuré las rutas para diseñar el uso del controlador personalizado en registros
devise_for :users, :controllers => { :registrations => ''UserRegistrations'' }
Luego generé un controlador para manejar el proceso de registro personalizado, copié el código fuente original del método create
en Devise::RegistrationsController
y lo modifiqué para que funcionara a mi manera (no olvide mover sus archivos de vista a la carpeta apropiada, en my case 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
Lo que básicamente hace todo esto es que el controlador determina qué tipo de usuario debe crearse de acuerdo con el parámetro user_type
que se entrega al método create
del controlador por el campo oculto en la vista que usa el parámetro mediante un simple parámetro GET en la URL.
Por ejemplo:
Si va a /users/sign_up?user[user_type]=designer
, puede crear un Diseñador.
Si va a /users/sign_up?user[user_type]=customer
, puede crear un Cliente.
my_devise_error_messages!
método es un método auxiliar que también maneja los errores de validación en el modelo asociativo, basado en el original devise_error_messages!
método
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
ACTUALIZAR:
Para poder admitir rutas como /designer/sign_up
y /customer/sign_up
, puede hacer lo siguiente en su archivo de rutas:
# routes.rb
match ''designer/sign_up'' => ''user_registrations#new'', :user => { :user_type => ''designer'' }
match ''customer/sign_up'' => ''user_registrations#new'', :user => { :user_type => ''customer'' }
Cualquier parámetro que no se use internamente en la sintaxis de las rutas se pasa al hash de parámetros. Entonces :user
pasa al hash de parámetros.
Eso es todo. Con un poco de tweeking aquí y allá lo conseguí trabajando de una manera bastante general, que es fácilmente extensible con muchos otros modelos de usuarios que comparten una tabla de usuario común.
Espero que alguien lo encuentre útil.
Primero, busqué intensamente con Google y Yahoo y encontré varias respuestas sobre temas como el mío, pero en realidad no cubren todo lo que necesito saber.
Tengo varios modelos de usuario en mi aplicación, por ahora son Clientes, Diseñadores, Minoristas y parece que aún quedan más. Todos ellos tienen diferentes datos almacenados en sus tablas y varias áreas en el sitio que están permitidas o no. Así que pensé en ir de la forma + CanCan y probar suerte con asociaciones polimórficas, así que obtuve la siguiente configuración de modelos:
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
Para el registro, tengo vistas personalizadas para cada tipo de Usuario diferente y mis rutas se configuran así:
devise_for :customers, :class_name => ''User''
devise_for :designers, :class_name => ''User''
devise_for :retailers, :class_name => ''User''
Por ahora, el controlador de registros se deja como estándar (que es "diseño / registros"), pero pensé que, dado que tenía diferentes datos para almacenar en diferentes modelos, ¿tendría que personalizar este comportamiento también?
Pero con esta configuración obtuve ayudantes como customer_signed_in?
y designer_signed_in?
, pero lo que realmente necesito es un ayudante general como user_signed_in?
para las áreas en el sitio que son accesibles para todos los usuarios, sin importar qué tipo de usuario.
También me gustaría un ayudante de rutas como new_user_session_path
lugar de varios new_*type*_session_path
y así sucesivamente. De hecho, todo lo que necesito para ser diferente es el proceso de registro ...
Así que me preguntaba si esta es la forma de ir para este problema ??? ¿O hay una solución mejor / más fácil / menos debe personalizar para esto?
Gracias por adelantado,
Robert
Estaba siguiendo las instrucciones anteriores y descubrí algunos vacíos y las instrucciones estaban desactualizadas cuando lo estaba implementando.
Así que después de luchar con ello todo el día, permítanme compartir con ustedes lo que funcionó para mí, y con suerte les ahorrará algunas horas de sudor y lágrimas.
En primer lugar, si no está familiarizado con el polimorfismo RoR, revise esta guía: http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/ Después de http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/ habrá diseñado y instalado los modelos de usuarios y podrá comenzar a trabajar.
Después de eso, siga el gran tutorial de Vapire para generar las vistas con todos los partails.
Lo que encontré más frustrante fue que al usar la última versión de Devise (3.5.1), RegistrationController se negó a trabajar. Aquí está el código que lo hará funcionar de nuevo:
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
y también agregue estas anulaciones para que las redirecciones funcionen bien:
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
Para que los mensajes flash sigan funcionando, deberá actualizar
config/locales/devise.en.yml
lugar de RegistrosControlloer reemplazados por UserRegistraionsControlloer, todo lo que tendrá que hacer es agregar esta nueva sección:user_registrations: signed_up: ''Welcome! You have signed up successfully.''
Espero que les ahorre algunas horas.
No pude encontrar ninguna manera de comentar la respuesta aceptada, así que voy a escribir aquí.
Hay un par de cosas que no funcionan exactamente como dice la respuesta aceptada, probablemente porque están desactualizadas.
De todos modos, algunas de las cosas que tuve que resolver por mi cuenta:
- Para UserRegistrationsController,
render_with_scope
ya no existe, solo usarender :new
La primera línea en la función create, nuevamente en UserRegistrationsController no funciona como se indica. Solo intenta usar
# 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
en lugar de simplemente build_resource
. Algún error de asignación masiva estaba apareciendo sin cambios.
- Si desea tener toda la información del usuario en el método current_user de Devise, realice estas modificaciones:
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