Using has secure password on a rails 4 app - ruby-on-rails

I'm trying to use has_secure_password for user login, I've defined the User mode as below
require 'digest/md5'
class User < ActiveRecord::Base
has_secure_password
before_validation :prep_emailId
before_save :create_avatar_url
validates :emailId, presence: true, uniqueness: true, format: { with: /\A(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+#((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})\z/i }
validates :first_name, presence: true
has_many :projects
belongs_to :nationality
belongs_to :category
scope :sorted, lambda{order("projects.position ASC")}
scope :newest_first, lambda{ "projects.created_at DESC"}
scope :oldest_first, lambda{order("projects.created_at ASC")}
scope :search, lambda{|query|
where(["name LIKE?", "%#{query}%"])
}
private
def prep_emailId
self.emailId = self.emailId.strip.downcase if self.emailId
end
def create_avatar_url
self.avatar_url = "http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(self.emailId)}?s=50"
end
end
I've declared strong parameters on the controller
def user_params
params.require(:user).permit(:category_id, :nationality_id, :first_name,
:last_name, :gender, :date_of_birth, :emailId, :password,
:password_confirmation, password_digest, :avatar_url)
end
Here's my create method.
def create
#user = User.new(user_params)
if #user.save
redirect_to user_path(#user.id)
#notice: "Thanks you for signing up !!!"
else
render ('new')
end
end
The error I'm getting when I try to save is as follows
Password digest missing on new record
Now if I take out attr_accessor from this code as suggested by many on stackoverflow this is what I end up getting.
Mysql2::Error: Data too long for column 'password_digest' at row 1: INSERT INTO `users` (`avatar_url`, `created_at`, `emailId`, `first_name`, `last_name`, `password_digest`, `updated_at`) VALUES ('http://www.gravatar.com/avatar/f76ca3885ff46187f3a216ba566623b9?s=50', '2014-03-17 10:39:01', 'funny#funnier.com', 'funny', 'funnier', '$2a$10$lJp6l70lHepWGz08f4O7luT3kE6Wj7bYzqD3o6G.EErkl0FTbAiHq', '2014-03-17 10:39:01')

You don't need the attr_accessors as has_secure_password handles that and the validation. You'll want password_confirmation in the view not password confirm.

Related

Partial validations in multistep forms (Wizard)

I have a multistep form, which I created with wizard. Basically the first tep of the form is user/sign_up - which in my understanding not a step yet. After hitting the sign-up button, user moves to the "real" first step, which is :address.
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :address
def show
#user = current_user || User.from_omniauth(request.env["omniauth.auth"])
render_wizard
end
def update
#user = current_user || User.from_omniauth(request.env["omniauth.auth"])
#user.update!(user_params)
render_wizard #user
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :street, :house_number, :city, :zip_code)
end
def redirect_to_finish_wizard(options = nil, params = nil)
redirect_to new_user_profile_path(current_user)
end
end
This is basically the end of the form already. All gets saved to the user. Now I am stuck with validations.
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:omniauthable, omniauth_providers: %i[facebook]
has_one :profile, dependent: :destroy
after_create :create_profile
accepts_nested_attributes_for :profile
validates :first_name, presence: true
validates :last_name, presence: true
validates :street, presence: true
validates :house_number, presence: true
validates :city, presence: true
validates :zip_code, presence: true
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0, 20]
name = auth.info.name
user.first_name = name.split(" ")[0]
user.last_name = name.split(" ")[1]
end
end
end
I would love to work with the the conditional validations in my model and only validate presence if on a certain step. This should be easy, as I theoretically only have one step, which is address. All I find on the internet, is way too complicated. Question is, do I have to somehow change user/sign_up to a first step in the form and address would be the second step? Or is it fine like this? And if so, can I just add the "if" statements to the address attributes in my validations, somehow defining what is the address step? Would it work like this?
def on_address_step?
wizard.steps = wizard.steps.first
end
Or how do I define it? The validations would look like this then:
validates :first_name, presence: true
validates :last_name, presence: true
validates :street, presence: true, if: :on_address_step?
validates :house_number, presence: true, if: :on_address_step?
validates :city, presence: true, if: :on_address_step?
validates :zip_code, presence: true, if: :on_address_step?
This is surely not that easy. For now this also doesn't work. How do I need to change it? Thanks.
P.S: here is also my Users Controller:
class UsersController < ApplicationController
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
redirect_to user_steps_path
else
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :street, :house_number, :city, :zip_code)
end
end
If filling in the address is a completely separate process I would just branch the address out into its own model and controller.
class User < ApplicationRecord
# ...
has_one :address
end
class Address < ApplicationRecord
# ...
belongs_to :user
validates :first_name, :last_name, :street,
:house_number, :city, :zip_code, presence: true
end
This avoids turning your user model into even more of a god object and removes the need for the conditional validation that makes your model much more aware of the UX steps than it should be.
# routes.rb
resources :addresses, only: [:new, :create]
class UsersController < ApplicationController
# ...
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
redirect_to new_address_path
else
render :new
end
end
end
class AddressesController < ApplicationController
# You should have some sort of method that checks if the user
# is signed in and redirect otherwise
before_action :authenticate_user!
# GET /addresses/new
def new
# I'm assuming you have some sort of method to fetch the signed in user
#address = current_user.build_address
end
# POST /addresses
def create
#address = current_user.build_address(address_params)
if #address.save
redirect_to '/somepath'
else
render :new
end
end
def address_params
params.require(:address).permit(
:first_name, :last_name, :street,
:house_number, :city, :zip_code
)
end
end
<%= form_with(model: #address) %>
# ... inputs
<% end %>
I doubt you really want the complexity involved with using Wicked which is ok if you really need a long multiple step form but in this case there is a far simpler and better design choice.

How do I seed data of a class which requires the user_id of the current user?

I am making a fitness web application as part of a project. This project has 5 models. User, Muscle_Groups, Diet, Workout and Meal. The associations are on the code below. As of now, I have a new page and a show page for the User. I want to redirect the user from the show page to the muscle_group index page where it will list all the muscles in a persons body. The User obviously has many Muscle_Groups, I want to seed the muscle_group index page with all muscle groups (biceps, back, legs, chest). The issue is, I need to create these instances with the user_id of the current user using the app and I have no idea how to do it at this point. I hope my brief explanation helps, my code is below.
class User < ApplicationRecord
has_many :diets
has_many :muscle_groups
before_save { self.email = email.downcase }
#before saving, the email is lowercased
validates :name, presence: true, length: { maximum: 50 }
#validates the names presence, max char length is 50
validates :weight, presence: true, numericality: true
validates :height, presence: true, numericality: true
validates_inclusion_of :gender, :in => %w( m f male Male female Female)
validates :age, presence: true, numericality: {only_integer: true }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
end
class Muscle_Group < ApplicationRecord
has_many :workouts
belongs_to :user
end
class Diet < ApplicationRecord
belongs_to :user
has_many :meals
end
class Meal < ApplicationRecord
belongs_to :diet
end
class Workout < ApplicationRecord
belongs_to :muscle_group
end
class UsersController < ApplicationController
def new #render's the sign up page for a new user
#user = User.new
end
def create #action to create the user
#user = User.create(user_params)
if #user.save
log_in #user
flash[:success] = "Are you ready to GitFit?!"
redirect_to #user #redirects to the user's show page, which is the main menu
else
render 'new'
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name,
:email,
:weight,
:height,
:gender,
:age,
:password,
:password_confirmation)
end
end
class Muscle_GroupsController < ApplicationController
def index
#muscle_groups = Muscle_Group.all
end
end
Just create one sample user in the seed file and associate the muscle groups to him. Then login with this account and you will have the results.
For instance like this
# seed.rb
sample_user = User.create(name: "Joe", weight: 100, height: 180,
age: 23, email: "test#mail.com",
password: "123456")
MuscleGroup.create(user: sample_user, ...)
Diet.create(user: sample_user)
...
I don't know the exact fields and the measuring system you use, but it could look like that.
Another way in production would be to sign up as a user of the website. And then find yourself in the console (rails c) and add the muscle_group and diet and so on and connect it manually to your user.

Rails 4 Devise nested form can't mass-assign protected attributes

I have a devise model that has a nested form (supp_form is the nested object) on sign up. When I submit the form I am getting the following error:
WARNING: Can't mass-assign protected attributes for Business: supp_form_attributes, terms_of_service
app/controllers/businesses/registrations_controller.rb:11:in `create'
I am using the nested_form gem and it seems as if my form is passing field data through to the console. My parameters after submit look like the following:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XXX", "business"=>{"type"=>"Business", "supp_form_attributes"=>{"title"=>"mr.", "first_name"=>"jane", "last_name"=>"doe", "mobile_phone_number"=>"94034903", "loan_agreement_authorization"=>"1", "work_phone_number"=>"49034903", "business_industry"=>"Natural Resources and Mining", "legal_structure"=>"Sole Proprietorship", "employee_count"=>"5 to 10", "years_in_business"=>"5+ years", "business_address"=>"72 pentland rd", "business_city"=>"Waterdown", "business_postal_code"=>"l0r2h5", "business_province"=>"ON"}
business.rb
class Business < User
# Associations
has_one :supp_form
has_many :loan_applications
has_many :transactions
# Nested attributes
accepts_nested_attributes_for :supp_form, :loan_applications
# After save action
after_save :create_account
# Validations
validates_acceptance_of :terms_of_service
validate :terms_of_service, presence: true
end
supp_form.rb
class SuppForm < ActiveRecord::Base
# Associations
belongs_to :business
# Validations
validates_acceptance_of :terms
validates :business_id, :first_name, :last_name, :work_phone_number, :business_address, :business_postal_code, :business_city, presence: true
end
registraionts_controller.rb
class Businesses::RegistrationsController < Devise::RegistrationsController
before_filter :update_sanitized_params
def new
build_resource({})
resource.build_supp_form
respond_with self.resource
end
def create
super
resource.update_attribute(:railsid, '%010d' % rand(10 ** 10))
end
private
def update_sanitized_params
devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, :type, :confirmed_at, :business_name, :terms, :railsid, :terms_of_service,
supp_form_attributes: [:business_id, :title, :loan_agreement_authorization, :first_name,
:last_name, :work_phone_number, :business_address, :business_postal_code,
:business_city, :business_name, :years_in_business, :legal_structure,
:business_industry, :employee_count, :mobile_phone_number, :business_province])}
end
def after_sign_up_path_for(resource)
business_root_path
end
end
supp_forms_controller.rb
class SuppFormsController < ApplicationController
before_filter :authenticate_user!
def new
#suppform = SuppForm.new(supp_form_params)
end
def create
#suppform = SuppForm.create(supp_form_params)
end
private
def supp_form_params
params.require(:supp_form).permit(:business_id, :title, :loan_agreement_authorization, :first_name,
:last_name, :work_phone_number, :business_address, :business_postal_code,
:business_city, :business_name, :years_in_business, :legal_structure,
:business_industry, :employee_count, :mobile_phone_number, :business_province)
end
end
You are using Rails 4 with strong parameters. And you get an error triggered by the protected_attributes gem (or default rails 3 app).
With strong_parameters on place you can remove safety the protected_attributes gem. And remove the configuration if you have it (config.active_record.whitelist_attributes).

has_one, reject_if, and accept_nested_attributes_for still triggers validation

I'm trying to provide a place to set a single service login for an account, yet not require that the account owner enter the service login credentials every time the rest of the record is updated.
My understanding is that the :reject_if option on accepts_nested_attributes_for is the way to have the nested hash values ignored. Yet, in Rails 4.1, I'm getting a "password can't be blank".
I've traced through the nested_attributes code and it seems to properly ignore the values, yet nothing I do to avoid the update works. I've even deleted the web_service_user_attributes hash from the params passed to update, so I'm wondering if there is something else going on.
Am I understanding :reject_if correctly for a has_one association?
Parent model code:
class Account
has_one :web_service_user
accepts_nested_attributes_for :web_service_user, :allow_destroy => true, :reject_if => :password_not_specified, :update_only => true
def password_not_specified(attributes)
attributes[:password].blank?
end
end
Child model code:
class WebServiceUser
devise :database_authenticatable
belongs_to :account
validates_uniqueness_of :username
validates_presence_of :password, if: Proc.new{|wsu| !username.blank? }
end
Controller code:
def update
respond_to do |format|
if #licensee.update(account_params)
#etc...
end
private
def account_params
params.require(:account).permit(:name, :area_of_business, :address1, :address2, :city, :state_code, :zip, :website_url, :web_service_user_attributes => [:id, :username, :password, :_destroy])
end
Ok, it appears that my primary goof was trying to validate the presence of :password. I really wanted to validate the length of the password if it existed.
class WebServiceUser
devise :database_authenticatable
belongs_to :account
validates_uniqueness_of :username
validates_length_of :password, :minimum => 14, if: Proc.new { |u| !u.password.nil? }
end

Rails how to get associated model attributes

I have the method below which saves data to the users table as well as the user_details table.
When i pass the #newUser variable to the EmailMailer, i can't access the user_details attributes. How can i pass the user_details in the #newUser object without having to re-query the database?
Models
class User < ActiveRecord::Base
has_one :user_details, :dependent => :destroy
accepts_nested_attributes_for :user_details
attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :login, :home_phone, :cell_phone, :work_phone, :birthday, :home_address, :work_address, :position, :company, :user_details_attributes
end
class UserDetails < ActiveRecord::Base
belongs_to :user
attr_accessible :first_name, :last_name, :home_phone, :cell_phone, :work_phone, :birthday, :home_address, :work_address, :position, :company
end
Controller
# POST /users
def create
#newUser = User.new(params[:user], :include =>:user_details)
# create password
require 'securerandom'
password = SecureRandom.urlsafe_base64(8)
#newUser.password = password
respond_to do |format|
if #newUser.save
#newUser.build_user_details
# Tell the UserMailer to send a welcome Email after save
EmailMailer.welcome_email(#newUser).deliver
# To be used in dev only. Just tests if the email was queued for sending.
#assert ActionMailer::Base.deliveries.empty?
format.html {
flash[:success] = "User created successfully"
redirect_to(contacts_path)
}
else
format.html {
flash[:error] = flash[:error].to_a.concat resource.errors.full_messages
redirect_to(contacts_path)
}
end
end
end
Something like this might do what you are after.
class User < ActiveRecord::Base
has_one :user_details
accepts_nested_attributes_for :user_details
after_initialize :build_user_details
...
end
# In controller
def create
#new_user = User.new
#new_user.attributes = params[:user]
if #new_user.save
# do mail thing
else
# other thing
end
end
You need to build the UserDetails association prior to saving #newUser
#newUser.build_user_details
if #newUser.save
#send mailer
else
#do something else
end
Alternatively you could use the create action after the #newuser is saved
if #newUser.save
#newUser.create_user_details
#send mailer
else
#do something else
end
By the way, Ruby/Rails convention is to use snake_case for variables. so #newUser should be #new_user.

Resources