Allow updating an attribute of a User model only once - ruby-on-rails

An admin account creates a user account, this automatically generates a username determined by the user's first and surname.
Once the username is created I want the user to be able to update it only once - Note: This can be at any time or after multiple logins, but once the username has been changed once it needs to be set in stone.
Using Rails and simple_form. The edit form is below.
= simple_form_for(current_user) do |f|
= f.error_notification
.form-inputs
= f.input :name, required: true, label: 'Name'
= f.input :username, required: true
= f.input :email, required: true
= f.input :password, required: true, placeholder: ("#{#minimum_password_length} characters minimum" if #minimum_password_length)
= f.input :password_confirmation, required: true
.form-actions
= f.button :submit, "Update Profile", class: 'btn btn-primary btn-block btn-lg'
Admin/Users controller.
def create
#user = User.new(user_params)
#user.password = SecureRandom.base64()
nameArray = #user.name.split(' ')
#user.username = nameArray[0][0].downcase + nameArray[-1][0..6].downcase
respond_to do |format|
if #user.save
#user.send_reset_password_instructions
format.html do
redirect_to [:admin, #user],
notice: "#{#user} was successfully created. A confirmation email was sent to: #{#user.email}"
end
else
format.html do
flash[:alert] = "User #{#user} already exists, or you have not filled in all of the required fields."
render :new
end
end
end
end
Thanks in advance.

Add new column to User like :edited
def change
add_column :users, :eited, :boolean, default: false
end
In your model User add calback after_update
class User < ApplicationRecord
after_update :set_edited!
before_update :check_if_edited!
def is_edited?
edited?
end
private
def check_if_edited!
# there you can set the error message or what you want
errors.add(:base, :invalid, message: "Your message")) if edited?
end
def set_edited!
transaction do
self.edited = true
self.save!
end
end
end
In your user controller add restriction to update user if is_edited?
def update
if current_user.is_edited?
// preform action
end
end
P.S. migrate logic to assign attributes to model User
class User < ApplicationRecord
after_create :assign_attributes
private
def assign_attributes
transaction do
self.password = SecureRandom.base64()
name_array = name.split(' ')
self.username = nameArray[0][0].downcase + nameArray[-1][0..6].downcase
self.save!
end
end
end

According to me the best way would be to use call_back in user model:
before_update{|user|
user.write_user_name_changed unless user.created_at == user.updated_at
}
def write_user_name_changed
self.user_name_changed = params[:username]
true
end

Related

Mail_form gem not showing error for invalid email

I have working mail_form contact form with google's smtp service.
I satisfied almost with everything, but form not showing errors for invalid email.
There is an error appears if email field is blank, but if something present in the field and you press "Send message" button nothing happens - validation not allowing send message, but user not informed that something wrong.
contact.rb
class Contact < MailForm::Base
attribute :name
attribute :email, validate: URI::MailTo::EMAIL_REGEXP
attribute :message, validate: true
attribute :file, attachment: true
def headers
{
subject: "My Contact Form",
to: 'MYEMAIL#gmail.com',
from: %("#{name}" <#{email}>)
}
end
end
views/contacts/new.html.haml
= form_with model: #contact do |f|
= f.label :name
= f.text_field :name, required: true, class: "text-field"
= f.label :email
= f.text_field :email, required: true, class: "text-field", placeholder: "email#example.com"
= f.label :message
= f.text_area :message, rows: 6, required: true, class: "text-field"
= f.label :files, class: "inline-block p-1 pt-2"
= f.file_field :file, class: "block p-1 font-body text-sm"
= f.submit t('send_message'), class: "submit"
contacts_controller.rb
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
def create
#contact = Contact.new(contact_params)
#contact.request = request
if #contact.deliver
redirect_to contacts_path, notice: 'Message sent!'
else
flash.now[:error] = 'Could not send message'
render :new
end
end
private
def contact_params
params.require(:contact).permit(:name, :email, :message, :file)
end
end
I have tried many things to show error messages, which have worked in my previous projects, but can't make it work with mail_form contact form.

Something wrong with my validates in Rail 4?

I want to show my validates in "users.rb". Can anyone help?. I've watched some video tutorial then found myself that was not "define new" but I did not know how to resolve this.
user_controller.rb
class UsersController::ApplicationController
def login
if params[:user]
#notice = "LOGIN FAILED"
#email = params[:user][:mailaddress]
#password = params[:user][:password]
#user = User.find_by(mailaddress: #email)
if #user
#member = Member.find_by(user_id: #user.id)
if #member
#hash = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), #member.salt, #password)
if #hash == #member.hashed_password
# SUCCESS
#notice = "LOGIN SUCCESSFULL"
end
end
end
end
end
end
-------
user.rb
class User::ActiveRecord::Base
has_one :member
validates :mailaddress, presence: {message: "Email need inputed"} #want to show this validates in login.html.haml
end
---------------
login.html.haml
= form_for(:user, url:login_path) do |f|
%div.login
= f.label "Email", class:'control-label'
= f.email_field :mailaddress, class: 'form-control'
= f.label "Password", class: 'control-label'
= f.password_field :password, class: 'control-label'
%div.btn-are
= f.submit 'Finish', class: 'btn'
Validations works only on model create/update but not on some actions like login.
Please see the docs for more info.
You can show flash if some params are missing
user_controller.rb
class UsersController::ApplicationController
def login
if params[:user]
unless params[:user][:mailaddress]
flash[:error] = "Email is required"
render :your_login_path
end
#notice = "LOGIN FAILED"
#email = params[:user][:mailaddress]
#password = params[:user][:password]
#user = User.find_by(mailaddress: #email)
if #user
#member = Member.find_by(user_id: #user.id)
if #member
#hash = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), #member.salt, #password)
if #hash == #member.hashed_password
# SUCCESS
#notice = "LOGIN SUCCESSFULL"
end
end
end
end
end
end
- flash.each do |name, msg|
= content_tag :div, msg, class: name
= form_for(:user, url:login_path) do |f|
%div.login
= f.label "Email", class:'control-label'
= f.email_field :mailaddress, class: 'form-control'
= f.label "Password", class: 'control-label'
= f.password_field :password, class: 'control-label'
%div.btn-are
= f.submit 'Finish', class: 'btn'
The code above is untested, but should work.
Here is the flash documentation
But I'm strongly recommend to use devise to perform sign_in/sign_up of users.

Using attributes from one model in another Rails

I apologize that this is such a simplistic question, but I've been struggling with it for a while.
I have two related models - Tour & Reservation. "Tour" has a "days" attribute. I want to list the days in a select tag for the user to choose from in my "Reservation" view
I thought this might work:
(Reservations controller) #tour_days = Tour.where(:days => params[:days])
(Reservations #new) = f.select :days, #tours_days
However, I'm receiving the error undefined methoddays' `
class Reservation < ActiveRecord::Base
belongs_to :tour
end
class Tour < ActiveRecord::Base
has_many :reservations
end
.
class ReservationsController < ApplicationController
def index
end
def new
#reservation = Reservation.new
#tour = Tour.find(params[:tour_id])
#tour_days = Tour.where(:days => params[:days])
end
def create
#tour = Tour.find(params[:tour_id])
if #reservation.update_attribute(:t_shirt, params[:t_shirt]) == true || #reservation.update_attribute(:hat, params[:hat]) == true
#tour.amount = #tour.amount + 15
else
#tour.amount = #tour.amount
end
#reservation = Reservation.new(reservation_params)
if #reservation.save
Stripe.api_key = ENV["STRIPE_SECRET_KEY"]
Stripe::Charge.create(
:amount => #tour.amount, # amount in cents, again
:currency => "usd",
:card => params[:stripeToken]
)
flash[:success] = "Your reservation has been booked for #{#reservation.passengers} person(s). Please save this info."
redirect_to new_tour_reservation_path(#tour)
else
render 'new'
end
end
private
def reservation_params
params.require(:reservation).permit(:passengers, :t_shirt, :hat)
end
end
.
class ToursController < ApplicationController
def index
#tours = Tour.all
end
def new
#tour = Tour.new
end
def create
#tour = Tour.new(tours_params)
if #tour.save
flash[:success] = "Tour #{#tour.name} has been successfully added."
redirect_to new_tour_path
else
flash[:error] = "The tour #{#tour.name} was not successfully saved. Please try again"
render 'new'
end
end
def show
#tour = Tour.find_by(id: params[:id])
#reservation = Reservation.new
end
def edit
#tour = Tour.find_by(id: params[:id])
end
def update
#tour = Tour.find_by(id: params[:id])
if #tour.update_attributes(tours_params)
flash[:success] = "#{#tour.name} has been successfully updated."
redirect_to tours_path
else
flash[:error] = "#{#tour.name} has not been updated. Please try again."
render 'edit'
end
end
def delete
#tour = Tour.find_by(id: params[:id])
end
def destroy
#tour = Tour.find_by(id: params[:id])
if #tour.destroy
flash[:success] = "The #{#tour.name} has been successfully deleted."
redirect_to tours_path
else
flash[:error] = "The #{#tour.name} has not been deleted. Please try again."
render 'edit'
end
end
private
def tours_params
params.require(:tour).permit(:name, :amount, :days)
end
end
.
= bootstrap_form_for([:tour, #reservation], html: { class: 'form-horizontal', id: 'payment-form'}) do |f|
= f.alert_message 'Please fix the errors below:'
= f.select :passengers, options_for_select( (1..10).map { |n| n %1 == 0 ? n.to_i : n } )
= f.select :days, #tours_days
%fieldset.credit_card
%span.payment-errors
.control-group
= label_tag :card_number, 'Credit card number:', class: 'control-label'
.controls
= text_field_tag :card_number, nil, name: nil, class: 'span3', data: {stripe: 'number'}
.control-group
= label_tag :security_code, 'Security code:', class: 'control-label'
.controls
= text_field_tag :security_code, nil, name: nil, class: 'span3', data: {stripe: 'cvc'}
.control-group
= label_tag :exp_date, 'Expiration:', class: 'control-label'
.controls
= select_month(Date.today, {add_month_numbers: true}, class: 'span2', data: {stripe: 'exp-month'})
= select_year(Date.today.year, {start_year: Date.today.year, end_year: Date.today.year + 4}, class: 'span1', data: {stripe: 'exp-year'})
%fieldset.actions.control-group
.controls
= f.submit 'Sign up'
consider using accepts_nested_attributes_for
Create another model to encapsulate the days. Then associate it with the Reservation model.
class Reservation < ActiveRecord::Base
belongs_to :tour
has_and_belongs_to_many :days
accepts_nested_attributes_for :days, allow_destroy: true
end
class Day < ActiveRecord::Base
has_and_belongs_to_many :reservations
end
The Day model will have one attribute: name which will hold the names of the seven days
class ReservationsController < ApplicationController
def create
#reservation = Reservation.new(reservation_params)
if #reservation.save
redirect_to #save
else
render :new
end
end
private
#add the `days_attributes` to the `reservations_params`
def reservation_params
params.require(:reservation).permit(:passengers, :t_shirt, :hat, days_attributes[:id, name])
end
end
then in new.html.erb when you are creating reservations, you can get a drop down to select specific days. you can do something like:
f.select :days
if you opt to use nested_forms, you'd have to use boostrap_nested_form_for as the documentation suggests.

Unable to 'Sign In' or 'Sign Up' with authentication features (no Devise gem)

I am currently unable to Sign Up or Sign In a user through my application and I'm unable to figure out why. I am using nested parameters with my User/Profile models.
When I try to sign up a new user, I get proper flash message saying
"Invalid email or password".
I created my authentication from scratch (not using Devise!). I also have a 'forgot password'/'remember me' feature but I have not displayed that information below as I think it is irrelevant.
Here is the console log (it seems to be a rollback, but no specific error given):
{Parameters: {"utf8"=>"✓", "authenticity_token"=>"Ek4cgnR3FQePCg/A4Wqc3atinU+WwRNgj+5hpXsd4mY=", "user"=>{"email"=>"sign#up.co", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "profile_attributes"=>{"first_name"=>"101001010", "last_name"=>"10101110101", "linkedin"=>"www.linkedin.com/signup", "twitter"=>"www.twitter.com/signup"}}, "commit"=>"Sign Up"}
(0.1ms) begin transaction
User Exists (0.1ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'sign#up.co' LIMIT 1
(0.1ms) rollback transaction
Rendered users/new.html.haml within layouts/application (60.3ms)
Completed 200 OK in 158ms (Views: 75.9ms | ActiveRecord: 0.3ms)}
models/user.rb
class User < ActiveRecord::Base
...
has_one :profile
accepts_nested_attributes_for :profile
delegate :full_name, to: :profile
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
VALID_URL_REGEX = /^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9] {1,5})?(\/.*)?$/ix
validates :email, :uniqueness => true, :presence => true
validates :first_name, :presence => true, length: {minimum: 1}
validates :last_name, :presence => true, length: {minimum: 1}
validates :twitter, :format => { with: VALID_URL_REGEX, :multiline => true}
validates :linkedin, :format => { with: VALID_URL_REGEX, :multiline => true}
models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
def full_name
if first_name || last_name
"#{first_name} #{last_name}".squeeze(" ").strip
end
end
end
controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.build_profile
end
def create
#user = User.new(user_attributes)
if #user.save && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_url, notice: "Thanks for signing in"
else
flash.now.alert = "Invalid email or password"
render :new
end
end
private
def user_attributes
params.require(:user).permit(:email, :password,
:password_confirmation,
{profile_attributes: [:first_name, :last_name, :linkedin, :twitter]})
end
end
controllers/profiles_controller.rb
class ProfilesController < ApplicationController
before_action :find_profile
def show
end
def update
if #profile.update_attributes(profile_params)
redirect_to posts_path
end
end
def edit
end
private
def profile_params
params.require(:profile).permit(:first_name, :last_name, :linkedin, :twitter)
end
def find_profile
#profile = current_user.profile
end
end
controllers/sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.find_by_email(params[:email])
respond_to do |format|
if user && user.authenticate(params[:password])
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
format.html { redirect_to root_path, notice: "You're logged in!" }
format.js do
flash.now.notice = "You're signed in!"
render
end
else
flash.now.alert = "Email or password is invalid"
format.html { render :new }
format.js { render :new }
end
end
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Logged out!"
end
end
Here are the views (haml)
views/user/new.html.haml
%h1 Sign Up for a New Account
= simple_form_for #user, html: { class: 'form-horizontal' } do |f|
.well
.container
= f.input :email, html_input: {class: "form-control"}
= f.input :password
= f.input :password_confirmation
= f.fields_for :profile do |p|
= p.input :first_name
= p.input :last_name
= p.input :linkedin
= p.input :twitter
= label_tag :remember_me
= check_box_tag :remember_me, 1, params[:remember_me]
= f.submit "Sign Up", class: "btn btn-primary"
**views/sessions/new.html.haml
%h1 Sign In
= simple_form_for "", url: sessions_path, html: {class: "form-horizontal"} do |f|
= f.input :email
= f.input :password
= f.submit "Sign In", class: "btn btn-primary"
%br
%p
=link_to "Forgot your password?", new_password_reset_path
%p
No account, yet?
= link_to "Sign up", signup_path
This problem has been bugging me for quite some time. I'd like to test out some of the user functionality but I cannot do so as I'm unable to login. Right now there is only one User record in the database and I created that manually in console. I am not able to log in to that user record either.
Any help is greatly appreciated.
def create
#user = User.new(user_attributes)
if #user.save && user.authenticate(params[:password])
session[:user_id] = user.id
...
end
In this code you create an instance variable (#user), but you call authenticate on user. You should either use User.authenticate or #user.authenticate (depending on how you implemented the authenticate method in your model). You should also change session[:user_id] = user.id to session[:user_id] = #user.id.

Rails lower case field input for login

In rails in my database I have a user called me#gmail.com but if I attempt to login with ME#gmail.com. I can't. Clearly my field is not becoming lower cased before it attempts to login. I know I should do a...
before_save { |user| user.email = email.downcase }
But from what I understand that goes in the model. But I only have a sessions controller. Not a sessions model. So how should I achieve the same effect?
my current user model
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :name
attr_accessor :password
before_save :encrypt_password
before_save { |user| user.email = email.downcase }
before_save { |user| user.name = name.downcase }
before_create { generate_token(:auth_token) }
before_create { generate_token(:email_token) }
my current sessions controller
class SessionsController < ApplicationController
def new
end
def create
user = User.authenticate(params[:email], params[:password])
if user
if user.email_activation_token == true
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
redirect_to root_url, :notice => "Logged in!"
else
flash.now.alert = "You email has not yet been verified. Please click the link in your email."
render "new"
end
else
flash.now.alert = "Invalid email or password"
render "new"
end
end
the form field in question
= form_tag sessions_path do
%p
= label_tag :email
= text_field_tag :email, params[:email]
%p
= label_tag :password
= password_field_tag :password
%p.button
%input{name: "commit", type: "submit", value: "Log in"}
user = User.authenticate(params[:email].downcase, params[:password])
OR
In method authenticate smth like this:
User.where("LOWER(email) = LOWER(?)", email).first

Resources