I managed to get user account creation working however I am running into an issue when I try to log in to the newly created account:
undefined method `valid_password?'
and the error highlights
if #user_session.save!
I am Authlogic and BCrypt on Rails 5 and I do not know why or how to fix the error above. I used this guide in order to setup Authlogic and I am able to sign out but when trying to log in, I get the above error. From some searches, some people have been able to solve this by restarting Heroku but I am not using Heroku so I do not believe that would work for me. Another solution was to add some password fields but I believe the "has_secure_password" would supply those fields accordingly. Does anyone know why this error occurs and/or how to fix it? Thanks!
Users Model:
class User < ApplicationRecord
has_many :activities
has_secure_password
validates :first_name, presence: true, length: {minimum: 1}
validates :last_name, presence: true, length: {minimum: 1}
validates :email, presence: true, uniqueness: true, length: {minimum: 5}
validates :password_digest, length: {minimum: 6}
validates :password, :confirmation => true, length: {minimum: 4}
validates :password_confirmation, presence: true
#-----------------------New Stuff ---------------------------------------
acts_as_authentic do |c|
c.crypto_provider = Authlogic::CryptoProviders::BCrypt
end
#------------------------------------------------------------------------
#---------------Unsure if working--------------
#validates_presence_of :password, :on => :create
#validates_presence_of :email
#validates_uniqueness_of :email
#----------------------------------------------
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
User Sessions Controller:
class UserSessionsController < ApplicationController
def new
#user_session = UserSession.new
end
def create
#user_session = UserSession.new(user_session_params)
if #user_session.save!
flash[:success] = 'Welcome back'
redirect_to root_path
else
render :new
end
end
def destroy
current_user_session.destroy
flash[:success] = 'Goodbye'
#redirect_to root_path - Should redirect somewhere after logout
end
private
def user_session_params
params.require(:user_session).permit(:email,:password)
end
end
Current User and User Session Tables:
create_table "user_sessions", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "persistence_token"
t.string "password_digest"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
end
EDIT 04-11-17
After making the changes below, it worked for me and I was able to log out and in without the mentioned problems. I still do not know why it gave me the error but I suspect it is because I was using "has_secure_password" which does not have a function to check if the password is valid ("valid_password?").
Alright turns out I managed to fix it by removing "has_secure_password" and just solely relying on "acts_as_authentic". After that, I was able to log in successfully. Here's my updated code:
User Model:
class User < ApplicationRecord
has_many :activities
acts_as_authentic
validates :first_name, presence: true, length: {minimum: 1}
validates :last_name, presence: true, length: {minimum: 1}
validates :email, presence: true, uniqueness: true, length: {minimum: 5}
validates :password, :confirmation => true, length: {minimum: 4}
validates :password_confirmation, presence: true
#-----------------------New Stuff ---------------------------------------
#------------------------------------------------------------------------
#---------------Unsure if working--------------
#validates_presence_of :password, :on => :create
#validates_presence_of :email
#validates_uniqueness_of :email
#----------------------------------------------
def self.authenticate(email, password)
user = find_by_email(email)
if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
def encrypt_password
if password.present?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
end
end
end
User Table:
create_table "users", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "persistence_token"
t.string "password_digest"
t.string "crypted_password", null: false
t.string "password_salt"
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
end
Not all of the fields above are required but I have them as I'm still new to Rails. The main thing required was "crypted_password" and "password_salt". After making the above edits, I was able to log in and out successfully without the mentioned error.
Related
I'm trying to implement a User model with a 1-to-many association with articles. So I added a User.rb model with
class User < ApplicationRecord
has_many :articles
before_save { self.email = email.downcase }
validates :username, presence: true,
uniqueness: { case_sensitive: false },
length: { minimum: 3, maximum: 25 }
VALID_EMAIL_REGEX = /\A\S+#.+\.\S+\z/i
validates :email, presence: true,
uniqueness: { case_sensitive: false },
length: { maximum: 105 }, format: { with: VALID_EMAIL_REGEX }
end
Also added belongs_to: user in the Article model:
class Article < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
validates :title, presence: true, length: { minimum: 5 }
validates :text, presence: true, length: { minimum: 10, maximum: 400}
validates :user_id, presence: true
end
I ran this migration
rails generate migration add_user_id_to_articles
and this is my migration file
class AddUserIdToArticles < ActiveRecord::Migration[5.2]
def change
add_column :articles, :user_id, :integer
end
end
Here is the definition of my schema.rb:
ActiveRecord::Schema.define(version: 2019_02_26_124924) do
create_table "articles", force: :cascade do |t|
t.string "title"
t.text "text"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "comments", force: :cascade do |t|
t.string "commenter"
t.text "body"
t.integer "article_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["article_id"], name: "index_comments_on_article_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
And lastly my change function in the articles_controller.rb file:
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
I ran rails db:migrate so that shouldn't be a problem. I saw some people on Stackoverflow say I should just add
#article.user_id = current_user.id
but already did that and nothing happened.
I keep getting this error:
NoMethodError (undefined method `user_id' for #)
Did you mean? user
and I don't know what else to try. I'm starting with Ruby on Rails so keep that in mind.
Thanks a lot for reading, hopefully somebody knows a fix.
First, make sure your migration actually runs through; then your schema.rb should include the user_id column too.
Then, try changing the line creating the new article to:
#article = current_user.articles.new(article_params)
Alternatively, you can set
#article.user = current_user
Inside your Articles controller, you'll need to add user_id into your permitted parameters.
def article_params
params.require(:article).permit(:title, :text, :user_id)
end
I'm getting an ActiveRecord::AssociationTypeMismatch error on my self join in Rails 5 that I can't figure out how to fix.
It's a simple rails app where a user can share a quote by an Artist (such as David Bowie) about another Artist (such as Lou Reed). So, a quote might look like this:
Quote
Topic: David Bowie
Content: "He was a master."
Speaker: Lou Reed
I have a Quote model and an Artist model and the Topics and Speakers are defined as self joins on the Artist model.
Here are the models:
class Artist < ApplicationRecord
default_scope -> { order(name: :asc) }
belongs_to :user
has_many :spoken_quotes, class_name: "Quote", foreign_key: :speaker_id
has_many :topic_quotes, class_name: "Quote", foreign_key: :topic_id
validates :user_id, presence: true
validates :name, presence: true, length: { maximum: 60 },
uniqueness: { case_sensitive: false }
end
class Quote < ApplicationRecord
default_scope -> { order(created_at: :desc) }
belongs_to :user
belongs_to :speaker, class_name: "Artist"
belongs_to :topic, class_name: "Artist"
validates :speaker, uniqueness: {scope: :topic}
validates :topic, uniqueness: {scope: :speaker}
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 1200 }
validates :source, presence: true, length: { maximum: 60 }
end
Here's the database schema:
create_table "artists", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id", "created_at"], name: "index_artists_on_user_id_and_created_at"
t.index ["user_id"], name: "index_artists_on_user_id"
end
create_table "quotes", force: :cascade do |t|
t.integer "user_id"
t.integer "artist_id"
t.text "content"
t.string "source"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["artist_id", "created_at"], name: "index_quotes_on_artist_id_and_created_at"
t.index ["artist_id"], name: "index_quotes_on_artist_id"
t.index ["user_id", "created_at"], name: "index_quotes_on_user_id_and_created_at"
t.index ["user_id"], name: "index_quotes_on_user_id"
end
Here's the relevant code from my Quotes Controller:
def create
#quote = current_user.quotes.build(quote_params)
if #quote.save
flash[:success] = "Quote created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
def quote_params
params.require(:quote).permit(:content, :source, :topic, :artist_id)
end
And the dropdown for the Topic of a Quote (which is an Artist) on the new Quote form:
<%= f.collection_select :topic, Artist.all, :id, :name %>
The dropdown looks fine and appears to be creating the association correctly, but when I submit the form I get the following error:
Artist(#70317289606580) expected, got "15" which is an instance of String(#70317259521760)
And the error message highlights the first line in the create action:
#quote = current_user.quotes.build(quote_params)
Am I defining my params wrong? What is wrong about my create action to cause this error. I can't seem to figure it out after researching it a bunch and trying various solutions.
Lee -
Your Quote and Artist models look OK. Your schema, however, is wrong. It should look like:
create_table "artists", force: :cascade do |t|
t.integer "user_id"
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "quotes", force: :cascade do |t|
t.integer "user_id"
t.integer "speaker_id"
t.integer "topic_id"
t.text "content"
t.string "source"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Note speaker_id and topic_id instead of artist_id.
I'd need to see your stack trace to see what might be wrong with how you have other things set up.
BTW, have you fixed your params whitelist? This is wrong:
def quote_params
params.require(:quote).permit(:content, :source, :topic, :artist_id)
end
Since your params look like:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"7xXgP3T1ZyxVhnr9TtBxeuYSRLBiuX01JSkQ4m4rN9pBS1W0iW6TJtsS7KyvunpCIZFiFltmdEwZGIYqsnxbyw==", "quote"=>{"topic_id"=>"2", "speaker_id"=>"1", "content"=>"asdfadsf", "source"=>"http://fuzz.com"}, "commit"=>"Post"}
It should be:
def quote_params
params.require(:quote).permit(:content, :source, :topic_id, :speaker_id)
end
As a shot in the dark, try changing:
validates :speaker, uniqueness: {scope: :topic}
validates :topic, uniqueness: {scope: :speaker}
To:
validates :speaker_id, uniqueness: {scope: :topic_id}
validates :topic_id, uniqueness: {scope: :speaker_id}
I'll update with explanation if that's the problem.
Try to change your select with:
<%= f.collection_select :topic_id, Artist.all, :id, :name %>
and permit topic_id with on your controller:
params.require(:quote).permit(:content, :source, :topic_id, :artist_id)
You can pass on object as an argument for belongs_to association in rails c:
Quote.new(topic: Artist.first)
and Rails'll do rest, but you can't pass an object via http request, but instead you should pass an object's id.
Updated
I'm confused about how do you want to bind Quote with speaker(Artist) and topic(Artist) having the artist_id column on quotes table only? It seems you should have different columns to store these connections. And then you'll be able to use the name of the column for select's name.
I am trying to write my own validation for a date_of_birth attr on an Employee model and I fail to see what I am getting wrong, I'm sure it's something really daft and right under my nose. The code is as below and my error message is;
NoMethodError:
undefined method `<' for nil:NilClass
employee.rb
class Employee < ApplicationRecord
belongs_to :quote
validates_presence_of :first_name, :last_name, :email, :gender, :date_of_birth, :salary
validates :first_name, length: { minimum: 2, message: "minimum of 2 chars" }
validates :last_name, length: { minimum: 2, message: "minimum of 2 chars" }
validates_email_format_of :email, :message => 'incorrect email format'
validate :older_than_16
enum gender: [ :m, :f ]
private
def older_than_16
self.date_of_birth < Time.now-16.years
end
end
schema.rb
ActiveRecord::Schema.define(version: 20170620125346) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "employees", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "initial"
t.integer "gender"
t.date "date_of_birth"
t.integer "salary"
t.integer "quote_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["quote_id"], name: "index_employees_on_quote_id", using: :btree
end
employee_spec.rb
RSpec.describe Employee, type: :model do
describe 'validations' do
it { should validate_presence_of(:date_of_birth) }
it { should_not allow_value(Date.today-15.years).for(:date_of_birth) }
# it { should allow_value(Date.today-17.years).for(:date_of_birth) }
end
end
Your custom method matcher is called even for the first test but self.date_of_birth is actually nil so you see this error.
You have to check if the date_of_birth is not nil before compare it.
You also have to add a new entry to the errors collection if you consider your model invalid.
(Also check your condition, I use > instead of < to make your test pass)
def older_than_16
return if self.date_of_birth.nil?
if self.date_of_birth > Time.now-16.years
errors.add(:date_of_birth, "Should be at least 16 years old")
end
end
Devise 3.5.2, Rails 4.2.3
While logging in, I'm trying to pass a hidden role_id along with the email/password combination. I am allowing the same email to register again, on a different subdomain, which causes a different role_id to be passed. The email+role_id is the unique index for the user.
I can create a user, but cannot log in. When I submit the log in form, I am faced with the following error:
undefined method 'email' for #<ActionDispatch::Request:0x007fa21628bda0>
EDIT:
If anyone can explain the process of changing the email uniqueness validation to email+role_id (not either/or, but and), that's all I need to accomplish. Following that process properly may avoid this error.
Debugging info:
The POST parameters are as follows:
{"utf8"=>"✓",
"authenticity_token"=>"[FILTERED]",
"member"=>{"role_id"=>"1",
"email"=>"some.user#email.com",
"password"=>"[FILTERED]",
"remember_me"=>"0"},
"commit"=>"Log in"}
Here is my Member model:
class Member < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :lockable, :timeoutable, :omniauthable
belongs_to :role
def self.find_for_authentication(warden_conditions)
where(:email => warden_conditions[:email], :role_id => warden_conditions[:role_id]).first
end
end
In config/initializers/devise.rb, the following is set:
config.authentication_keys = [:email, :role_id]
config.request_keys = [:email, :role_id]
My views/devise/sessions/new.html.erb includes:
<%= f.hidden_field :role_id, :value => Role.find_by_name(current_subdomain).id %>
I adjusted vendor/bundle/ruby/1.9.1/gems/devise-3.5.2/lib/devise/models/validatable.rb by changing this line:
validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
to:
validates_uniqueness_of :email, :scope => :role_id, allow_blank: true, if: :email_changed? #using subdomains for validation
The relevant database migrations for the member are found here:
...devise_create_members.rb
class DeviseCreateMembers < ActiveRecord::Migration
def change
create_table(:members) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, default: 0, null: false
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :members, :email, unique: true
add_index :members, :reset_password_token, unique: true
add_index :members, :confirmation_token, unique: true
add_index :members, :unlock_token, unique: true
end
...add_columns_to_member.rb
class AddColumnsToMember < ActiveRecord::Migration
def change
add_reference :members, :contact, index: true
add_reference :members, :role, index: true
add_reference :members, :ownership, index: true
add_column :members, :account_status, :string
end
end
...reindex_members_email_and_role.rb
class ReindexMembersEmailAndRole < ActiveRecord::Migration
def change
add_index :members, [:email, :role_id], :unique => true
end
end
The last item on the trace is:
vendor/bundle/ruby/1.9.1/gems/devise-3.5.2/lib/devise/strategies/authenticatable.rb:152:in `block in request_values'
keys = request_keys.respond_to?(:keys) ? request_keys.keys : request_keys
values = keys.map { |k| self.request.send(k) } <--ERROR THIS LINE
Hash[keys.zip(values)]
end
What am I missing?
To fix this, I changed my config/initializers/devise.rb to reflect the following:
config.request_keys = { role_id: false }
This fixed the issue, but still prevented the same email from signing up with a different role ID. To fix this, I removed :validatable from my User model and added:
validates_uniqueness_of :email, :case_sensitive => false, :scope => :role_id, :allow_blank => true, :if => :email_changed?
validates_format_of :email, :with => Devise.email_regexp, :allow_blank => true, :if => :email_changed?
validates_presence_of :password, :on=>:create
validates_confirmation_of :password, :on=>:create
validates_length_of :password, :within => Devise.password_length, :allow_blank => true
This allows the same email address to sign up with a different role_id.
I also changed the following in authenticatable.rb:
def request_values
keys = request_keys.respond_to?(:keys) ? request_keys.keys : request_keys
values = keys.map { |k| self.request[self.scope][k] }
# values = keys.map { |k| self.request.send(k) }
Hash[keys.zip(values)]
end
UPDATE
I got tired of always having to re-hack the devise library, especially after I updated gems or transferred the app. I found this page that offered a better work-around (still follow the step regarding validations the User model listed above):
(From https://github.com/plataformatec/devise/pull/3965)
Comment out the following line we edited above:
# config.request_keys = { role_id: false }
Edit the config.authentication_keys line as follows:
config.authentication_keys = { email: true, role_id: false }
The issue is that request_keys honors only predefined keys such as :subdomain.
That should work now for creating a combination of custom keys to authenticate with.
I have read the posts here, here, and here, but I'm still having trouble with implementing Single Table Inheritance.
Ideally I would like to have two registration paths (one for clients and one for providers) with the common fields name, email, password, and confirm_password, and the provider registration having an extra radiobutton field to specify a provider type. I am doing the registration through devise. Upon clicking submit on the registration form a user would then be redirected to a second form which is totally different for clients and providers (I have been doing this using the edit page for a resource).
As it stands, everything works if I am just doing it through User, but as soon as I add single table inheritance the registration forms tell me they are missing the requirements of the second forms.
Here is my config/routes.rb
Rails.application.routes.draw do
devise_for :users, :controllers => {:sessions => "sessions"}, :skip=> :registrations
devise_for :clients, :providers, :skip=> :sessions
resources :clients
resources :providers
root :to=>'pages#home'
match '/home', to: 'pages#home', via: 'get'
end
My models look as follows:
User:
class User < ActiveRecord::Base
before_save {self.email = email.downcase}
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
validates :name, presence: true, length: {maximum: 50}
validates :email, presence: true, :email => {:ban_disposable_email => true, :message => I18n.t('validations.errors.models.user.invalid_email')}, uniqueness: { case_sensitive: false }
validates :password, presence: true, length: {minimum: 6},:if=>:password_validation_required?
LOGO_TYPES = ['image/jpeg', 'image/png', 'image/gif']
has_attached_file :avatar, :styles => {:medium => "300x300>",:square=>"200x200>", :thumb => "100x100>" }, :default_url => '/assets/missing_:style.png'
validates_attachment_content_type :avatar, :content_type => LOGO_TYPES
def password_validation_required?
!#password.blank?
end
end
Client:
class Client < User
validates :industry, presence: true
validates :city, presence: true
validates :state, presence: true
validates :description, presence: true, length: {minimum: 50, maximum: 300}
end
Provider:
class Provider < User
validates :ptype, presence: true
validates :city, presence: true
validates :state, presence: true
validates :education, presence: true
validates :biography, presence:true, length: {minimum: 50, maximum: 300}
validates_format_of :linkedin, :with => URI::regexp(%w(http https))
validates :resume, presence: true
has_many :disciplines
end
and here are my controllers:
class SessionsController < Devise::SessionsController
def create
rtn = super
sign_in(resource.type.underscore, resource.type.constantize.send(:find,resource.id)) unless resource.type.nil?
rtn
end
end
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
if resource.is_a?(User)
if current_user.is_a?(Client)
edit_client_path(current_user.id)
elsif current_user.is_a?(Provider)
edit_provider_path(current_user.id)
end
else
super
end
end
end
class ClientsController < ApplicationController
def show
#client = Client.find(params[:id])
end
def edit
#client = Client.find(params[:id])
end
def update
#client = Client.find(params[:id])
if #client.update_attributes(client_params_edit)
flash[:success] = "Profile Updated"
redirect_to #client
else
flash[:failure] = "Profile Information Invalid"
render 'edit'
end
end
def client_params_edit
params.require(:client).permit(:avatar,:industry,:city,:website, :description)
end
end
the provider controller is quite similar.
Finally, here is my schema.rb:
ActiveRecord::Schema.define(version: 20140628213816) do
create_table "disciplines", force: true do |t|
t.integer "years"
t.string "description"
t.integer "user_id"
end
create_table "users", force: true do |t|
t.string "name"
t.string "email"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.string "password_digest"
t.string "industry"
t.string "city"
t.string "state"
t.string "website"
t.string "description"
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "type"
t.string "ptype"
t.string "education"
t.string "resume_file_name"
t.string "resume_content_type"
t.integer "resume_file_size"
t.datetime "resume_updated_at"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
You need to specify which model should be instantiated inside your custom registrations controller (that one which inherits from Devise::RegistrationsController).
You have to override the protected method called resource_class to somewhat like this:
def resource_class
# for example you pass type inside params[:user]
klass = params[:user].try(:[], :type) || 'Client'
# we don't want wrong class to be instantiated
raise ArgumentError, 'wrong user class' unless ['Client', 'Provider'].include?(klass)
# transform string to class
klass.constantize
end
Also you might want to override sign_up_params to specify allowed params based on user type too.
Just a thought.
Have you considered allowing registration as a user and holding the type parameter back until later in the workflow.
i.e.
Registration page:
Creates User (with a parameter that decides which Type the user will end up being)
Second page (to which you are automatically redirected upon creating user, or even logging in as user having not gone through part 2):
Adds the appropriate required information and changes type from User to your appropriate STI type upon submit.
Other option would be to swap your first "submit" button for a button which simply reveals the relevant extra fields (and the real submit button) via JS.