I try to change my rails-3.2-model to rails 4, but I dont understand it.
Maybe you can help me to change it.
3.2:
class User < ActiveRecord::Base
attr_accessible :email, :username, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email, :on => :create
validates_presence_of :username, :on => :create
validates_uniqueness_of :email
validates_uniqueness_of :username
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
4.0.4:
class User < ActiveRecord::Base
validates :name, presence: true, uniqueness: {case_sensitive: true}, length: {minimum: 3, too_short: "must have at least %{count} characters"}
validates :email, presence: true, uniqueness: {case_sensitive: true}
validates :password_hash
end
I tried to get rid off the attr_accessible and attr_accessor, but I don't know how.
attr_accessor :password and attr_accessible [...] :password_confirmation are not stored in the database, so how can I use it in my view?
EDIT:
View:
<p>Sign Up</p>
<%= form_for #user, :as => :user, :url => auth_sign_up_path, :html => {:class => 'navbar-form', :role => 'login'} do |user_form_builder| %>
<p>
<%= user_form_builder.label 'name:' %><br/>
<%= user_form_builder.text_field :name %>
<%= show_field_error(#user, :name) %>
</p>
<p>
<%= user_form_builder.label 'email:' %><br/>
<%= user_form_builder.text_field :email %>
<%= show_field_error(#user, :email) %>
</p>
<p>
<%= user_form_builder.label 'password:' %><br/>
<%= user_form_builder.password_field :password %>
<%= show_field_error(#user, :password) %>
</p>
<p>
<%= user_form_builder.label 'password confirmation:' %><br/>
<%= user_form_builder.password_field :password_confirmation %>
<%= show_field_error(#user, :password_confirmation) %>
</p>
<p>
<%= user_form_builder.submit 'Sign Up' %>
<%= user_form_builder.submit 'Clear Form', :type => 'reset' %>
</p>
<% end %>
Controller:
def sign_up
#user = User.new
end
def register
#user = User.new(user_params)
if #user.valid?
#user.save
session[:user_id] = #user.id
flash[:notice] = 'Welcome.'
redirect_to :root
else
render :action => "sign_up"
end
end
private
def user_params
params.require(:user).permit(:email, :name, :password, :password_confirmation)
end
Model:
require 'bcrypt'
class User < ActiveRecord::Base
attr_accessor :name, :email, :password, :password_confirmation
before_save :encrypt_password
after_save :clear_password
validates :name, presence: true, uniqueness: {case_sensitive: true}, length: {minimum: 3, too_short: "must have at least %{count} characters"}
validates :email, presence: true, uniqueness: {case_sensitive: true}
validates :password, presence: true, length: {minimum: 8, too_short: "must have at least %{count} characters"}, :confirmation => true #password_confirmation attr
def initialize(attributes = {})
super # must allow the active record to initialize!
attributes.each do |name, value|
send("#{name}=", value)
end
end
def self.authenticate_by_email(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 self.authenticate_by_name(name, password)
user = find_by_username(name)
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
def clear_password
self.password = nil
end
end
Migration:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.string :password_hash
t.string :password_salt
t.string :cal_owner, :array => true, :default => '{}'
t.string :cal_joined, :array => true, :default => '{}'
t.timestamps
end
end
end
Routes:
Calendar::Application.routes.draw do
# You can have the root of your site routed with "root"
root 'welcome#index'
get "auth/sign_up" => "auth#sign_up"
get "auth/sign_in" => "auth#sign_in"
get "auth/sign_out" => "auth#sign_out"
get "auth/settings"
get "auth/pwd_reset"
get "welcome/index"
post "auth/sign_in" => "auth#login"
post "auth/sign_up" => "auth#register"
end
I used a tutorial, but I didnt know why the author add this:
def initialize(attributes = {})
super # must allow the active record to initialize!
attributes.each do |name, value|
send("#{name}=", value)
end
end
The author wrote:
For each key-value pair (hash) we assign the value to the attribute by
calling the "send" function (all method calls in Ruby are actually
messages.)
Important:
We don't actually need to do this for the User class because the
constructor provided by Rails will allow us to do a "mass assign" from
a hash as long as the fields that we are assigning have been
designated as "attr_accessible", which they have. However, there are
cases when one wants to initialize several fields (such as ID's in a
many-to-many table) that are not intended to be accessible to a view
but instead are designated with an "attr_accessor" instead. The above
function is a simple way of providing safe mass assign capability for
internal constructors.
From what I now understand, your user table has effectively 4 columns in addition to the ID, created at etc.:
name
email
password_hash
password_salt
On your view you have fields:
name
email
password
password_confirmation
Your controller looks correct. The user_params method is whitelisting the 4 fields from the view and letting them be passed through to the User model for creating a new user.
In your model you need to make 2 changes.
Firstly you should remove name and email from the attr_accessor line:
attr_accessor :password, :password_confirmation
You only need password and password_confirmation. The reason for this is since name and email are columns on the database, Rails will automatically give you the getter and setter methods for these attributes. attr_accessor then saves you having to explicitly write getter and setter methods for password and password_confirmation and lets them be populated automatically on creating a new user with the values that come through from the view.
Secondly you should remove the initialize method. User inherits from ActiveRecord::Base and will be able to build new user records quite happily without the need for a constructor.
The author of the tutorial included the constructor for doing mass-assignment. However in Rails 4 this has been changed to use strong parameters so that it is now the responsibility of the controller to state which parameters can be passed to the model. You do this correctly in your user_params method in the controller.
I hope this helps.
Related
So I'm creating a simple app for myself and a small group of people. I would like to restrict access to people that I hand-generate codes for by typing them in myself.
User sign up, log in, logout works, but I don't just want anyone to be able to be register.
TL;DR
User can sign up but how do I go about setting up a hand-generated code
Should be some way to invalidate that code after sign up and see who
the code is associated with
How can I do this in rails?
I know the user_model would have to add some sort of field to it, the view/form for it as well, and it would have to be validated (by the controller?). Just stuck.
My thought process is as follows (what i've thought so far)
All the invite codes should be kept as an array in a file in the
rails app?
I will have to add a migration that adds invite_code to the model/db/view form
The controller should validate the presence of the invite code in the view form?
user/new.html.erb
<%= form_for(#user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Retype Password" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create My Account" %>
<% end %>
users_controller.rb
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome!"
redirect_to #user
# Handle a successful save.
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
user.rb model
class User < ActiveRecord::Base
# links this to the question.rb model
has_many :questions, dependent: :destroy
attr_accessor :remember_token
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[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
validates :password, length: { minimum: 6 }
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# returns a random token
def User.new_token
SecureRandom.urlsafe_base64
end
# remembers a user in the database for use in persistent sessions
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# returns true if the given token matches the digest
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
end
Well, In that case, you can have a model say Token created as below:
def self.up
create_table :tokens do |t
t.string :code, null: false
t.boolean :used, default: false
t.timestamps
end
end
And a migration in your User table for token_id
You can generate new code like, Token.generate_new_code
def self.generate_new_code
token = Token.new(code: Digest::SHA1.hexdigest Time.now.to_s)
token.code if token.save
end
And you can understand the rest i.e. accept code from registration form, validate for unused code and set it true and token_id if the registration is successful.
I am using devise for user authentication in a rails 4 app.
After the user registers, I am redirecting the user to a page which has some additional fields they can choose to populate. I have the form appearing correctly, but it is not saving the nested attribute to the database.
I have a model called "seeker_skill" which has this relation to user:
user has_many seeker_skills
seeker_skills belongs to user
user.rb
class User < ActiveRecord::Base
has_many :seeker_skills, dependent: :destroy
accepts_nested_attributes_for :seeker_skills, :reject_if => lambda { |a| a[:skill].blank? }, :allow_destroy => true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
users_controller.rb
class UsersController< ApplicationController
def job_seeker_additional_fields
#user = current_user
#user.seeker_skills.build
#seeker_skill = current_user.seeker_skills.build
end
end
seeker_skill.rb
class SeekerSkill < ActiveRecord::Base
belongs_to :user
validates :skill, presence: true
end
seeker_skills_controller.rb
class SeekerSkillsController < ApplicationController
def create
#seeker_skill = current_user.seeker_skills.build(seeker_skill_params)
if #seeker_skill.save
redirect_to root_url
else
flash[:error] = "Invalid Input"
redirect_to myskills_path
end
end
def destroy
end
def new
#seeker_skill = current_user.seeker_skills.build
#user = current_user
end
private
def seeker_skill_params
params.require(:seeker_skill).permit(:skill)
end
end
I believe I have the permitted parameters set up correctly in the application controller.
application_controller.rb
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :role, :email,
:company, :password, :password_confirmation, :remember_me,
seeker_skills_attributes: [:id, :skill, :user_id, :_destroy]) }
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:login,
:username, :role, :email, :company, :password, :remember_me) }
devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :bio,
:min_salary, :location, :radius, :role, :email, :company, :password, :password_confirmation,
:current_password, seeker_skills_attributes: [:id, :skill, :user_id, :_destroy]) }
end
end
Finally there is the form in the view: Eventually I will add option to add multiple skills at once.
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= f.fields_for(#seeker_skill) do |f| %>
<%= f.text_field :skill, placeholder: "Add skill" %>
<% end %>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
What am I missing? I have set this up with a custom user authentication system but never with devise.
Change your nested fields call to:
<%= f.fields_for(:seeker_skill) do |f| %>
with a symbol, not the object. When created with object, it names the field from the object class name, so in result you got params[:user][:seeker_skill], which are then filtered by strong params. While run with symbol, it tries to execute method with given name, treats it as an object and if the form object defines <name>_attributes sets the subobject name to <name>_attributes.
I'm trying to use a textfield as a place for users to edit their birthday as a date. With the example, i'm working on, no birthday exists yet. The birthday that i'm trying to add is 03/21/1986.
Here's the controller method:
# PUT /contacts/1/edit
# actually updates the users data
def update_user
#userProfile = User.find(params[:id])
#userDetails = #userProfile.user_details
respond_to do |format|
if #userProfile.update_attributes(params[:user])
format.html {
flash[:success] = "Information updated successfully"
redirect_to(edit_profile_path)
}
else
format.html {
flash[:error] = resource.errors.full_messages
render :edit
}
end
end
end
and here's the model method. You can see i'm calling a validation method on :birthday to convert it to a date. Everything seems to work, but nothing saves to the database and i get no errors.
# validate the birthday format
def birthday_is_date
p 'BIRTHDAY = '
p birthday_before_type_cast
new_birthday = DateTime.strptime(birthday_before_type_cast, "%m/%d/%Y").to_date
p new_birthday
unless(Chronic.parse(new_birthday).nil?)
errors.add(:birthday, "is invalid")
end
birthday = new_birthday
end
Here's the printout from my p statements in my model validation method
"BIRTHDAY = "
"03/21/1986"
1986-03-21 12:00:00 -0600
I've also just noticed that if i do a date of 10/10/1980, it works just fine, and if i do a date of 21/03/1986, i get an invalid date error.
EDIT
Here's some more info that may help:
view:
<%= form_for(#userProfile, :url => {:controller => "contacts", :action => "update_user"}, :html => {:class => "form grid_6 edit_profile_form"}, :method => :put ) do |f| %>
...
<%= f.fields_for :user_details do |d| %>
<%= d.label :birthday, raw("Birthday <small>mm/dd/yyyy</small>") %>
<%= d.text_field :birthday %>
...
<% end %>
user model
class User < ActiveRecord::Base
...
# Setup accessible (or protected) attributes for your model
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
validates_presence_of :email
has_one :user_details, :dependent => :destroy
accepts_nested_attributes_for :user_details
end
user_details model
require 'chronic'
class UserDetails < ActiveRecord::Base
belongs_to :user
validate :birthday_is_date
attr_accessible :first_name, :last_name, :home_phone, :cell_phone, :work_phone, :birthday, :home_address, :work_address, :position, :company
# validate the birthday format
def birthday_is_date
p 'BIRTHDAY = '
p birthday_before_type_cast
new_birthday = DateTime.strptime(birthday_before_type_cast, "%m/%d/%Y").to_date
p new_birthday
unless(Chronic.parse(new_birthday).nil?)
errors.add(:birthday, "is invalid")
end
birthday = new_birthday
end
end
def birthday_is_date
begin
birthday = DateTime.strptime(birthday_before_type_cast, "%m/%d/%Y").to_date
rescue
errors.add(:birthday, "is invalid")
end
end
I ended up using a virtual attribute and it seems to be working now.
I added this to my model
attr_accessible :birthday_string
def birthday_string
#birthday_string || birthday.strftime("%d-%m-%Y")
end
def birthday_string=(value)
#birthday_string = value
self.birthday = parse_birthday
end
private
def birthday_is_date
errors.add(:birthday_string, "is invalid") unless parse_birthday
end
def parse_birthday
Chronic.parse(birthday_string)
end
I'm trying to create a new page called edit_profile for my User model so that the user can edit his profile(string). I'm following http://railscasts.com/episodes/41-conditional-validations
Here is the form (edit_profile.html.erb):
<%= form_for #user, :html => { :multipart => true } do |f| %>
<div class="field">
<%= f.label :profile %><br/>
<%= f.text_area :profile, :class => "round" %><br />
</div>
<div class="actions">
<%= submit_tag "update", :id => "updateSubmit" %>
</div>
<% end %>
The problem I'm having is that I have validation for the presence of password and password confirmation. When I load my edit_profile view, I keep getting this message Password is too short (minimum is 6 characters) even before I try to submit a new profile.
Here is my users_controller.rb:
def edit_profile
#user = current_user
#user.updating_password = false
#user.save
#title = "Edit profile"
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
flash[:success] = "Account updated."
redirect_to #user
else
#title = "Edit user"
render 'edit'
end
end
How do I bypass my password validation when I just want to edit my profile attribute in the user model?
Thanks!
Other relevant information:
user.rb
class User < ActiveRecord::Base
attr_accessor :password, :updating_password
attr_accessible :name, :email, :password, :password_confirmation, :photo,
:profile
before_save :downcase_fields
before_save :encrypt_password
validates_presence_of :password, :if => :should_validate_password?
validates_confirmation_of :password, :if => :should_validate_password?
def should_validate_password?
updating_password || new_record?
end
validates :name, :presence => true,
:name_format => true,
:uniqueness => { :case_sensitive => false }
validates :email, :presence => true,
:email_format => true,
:uniqueness => { :case_sensitive => false }
validates :password,
#:presence => true,
#:confirmation => true,
:length => { :within => 6..40 }
validates :profile, :length => { :maximum => 160 }
end
Given you're talking about profile editing, i.e. you already have a registered user with password, you might want to conditionally skip password updating.
Just remove password and password_confirmation if password_confirmation is blank. I.e.:
class UsersController < ApplicationController
before_filter :skip_password_attribute, only: :update
...
def update
...
end
private
def skip_password_attribute
if params[:password].blank? && params[:password_validation].blank?
params.except!(:password, :password_validation)
end
end
end
(1) Typically, when #user is a new record, the form_for will go to create, and when the #user is not a new record it will go to update. If this fails to happen, then, you need to set the :url, an maybe the :method.
<%= form_for #user, :url => (#user.new_record? ? users_path : user_path(#user),
:html => (#user.new_record? ? { :multipart => true, :method => :post } : { :multipart => true, :method => :put } do |f| %>
(2) What you asked for is
class User
validate :validate_password_length
def validate_password_length
!new_record? || password.length >= 8
end
end
however, that lets a new user create an account the change the password to something shorter. So it would be better to do the following:
class User
validate :validate_password_length
def validate_password_length
!password_changed? || password.length >= 8
end
end
I recommend devise for user auth & stuff. This is taken from the devise source (https://github.com/plataformatec/devise/blob/master/lib/devise/models/validatable.rb):
validates_presence_of :password, :if => :password_required?
validates_confirmation_of :password, :if => :password_required?
validates_length_of :password, :within => 6..128, :allow_blank => true
protected
def password_required?
!persisted? || !password.nil? || !password_confirmation.nil?
end
Here's how I ended up solving this problem, though I'm pretty sure it's not ideal - the validation method would need to be more complex if you have additional password-strength rules.
Basically, you don't actually have to change the controller at all - you can fix up your model to handle this scenario.
First, you can set the :on => :create attribute on your password and password_confirmation fields. However, this has the side effect that users must supply a compliant password at registration, but can then change it to a non-compliant one later. To resolve this, I added a before_update callback to validate the password field when doing an update. My User class now looks something like this:
class User < ActiveRecord::Base
...
before_update :check_password
...
validates :password, presence: true,
length: { minimum: 6 },
on: :create
validates :password_confirmation, presence: true,
on: :create
...
private
...
def check_password
is_ok = self.password.nil? || self.password.empty? || self.password.length >= 6
self.errors[:password] << "Password is too short (minimum is 6 characters)" unless is_ok
is_ok # The callback returns a Boolean value indicating success; if it fails, the save is blocked
end
end
As noted in the comment above, the result of this method determines whether the save will be attempted. In order to prevent the user being dumped back into the edit form without an error message, I inject one. [:password] tells it which field to attach the error to.
You just need to add :on => :create to validate.
I have an edit form for updating some attributes. But it tryes to edit all attributes, and cause of this I have validation error.
My form (edit view)
#person_info.fl_l
- if #user.errors.any?
.error_explanation
%h2 Form is invalid
%ul
-for message in #user.errors.full_messages
%li= message
=form_for #user do |f|
%p
Birthday:
%br
= f.date_select(:birthday,:start_year => 1940)
%p
Name:
%br
= f.text_field :name, :value=>#user.name
%p
Surname:
%br
= f.text_field :surname, :value=>#user.surname
%p
Nickname:
%br
= f.text_field :nickname, :value=>#user.surname
%p
About Myself:
%br
= f.text_area :about_myself, :value=>#user.about_myself
%p
= f.submit "Update"
My update and edit actions:
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
redirect_to #user
else
render 'edit'
end
end
When I submit the form, it outputs validation errors like: "Password can't be blank"
So, how to update only part of attributes, not all? I dont want to update password in my case.
My user model
class User < ActiveRecord::Base
has_many :posts
has_many :sent_messages, :class_name => "Message", :foreign_key => "sender_id"
has_many :received_messages, :class_name => "Message", :foreign_key => "receiver_id"
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => {:maximum => 50}
validates :email, :presence => true,
:format => {:with => email_regex},
:uniqueness => {:case_sensitive => false}
validates :password, :presence => true,
:confirmation => true,
:length => {:within => 6..40}
before_create { generate_token(:auth_token) }
def send_password_reset
generate_token(:password_reset_token)
self.password_reset_sent_at = Time.zone.now
save!
UserMailer.password_reset(self).deliver
end
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
end
Remove it from the params hash
params.delete(:password)
Or build your own hash to send to update.
Checkout Authentication in Rails 3.1 to smoothen out your authentication. it takes out a lot of code from your model.
Then, I noticed that: if a field is hidden from the form for any reason, rails does not pass this in the params hash for an update. So you can play with hiding fields in a form. This is how I solve mine.
I fixed this issue by appending
,:on => :create
to my validates_presence_of and validates_length_of lines.
I believe your model is invalid in its current state ... you're not sending in the password, so Rails is not touching that attribute.
In rails console, test:
user = User.find(whatever_the_id_is)
puts user.valid?
puts user.errors.inspect
My hypothesis is that it is invalid --- due to a missing password.
I have solved this in the User password validation like so:
validates :password, :presence => true,
:confirmation => true,
:if => :password # only validate if password changed!