How to update only part of attributes, not all, in rails - ruby-on-rails

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!

Related

rails 3.2 model convert to rails 4

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.

Rails 3 Active record validation error messages with unwanted/extra characters

I'm workin on ROR app using Rails 3.2.9 and I'm getting the error messages for a sign up page in my app as follows
<li>Login is too short (minimum is 3 characters)</li><li>Email is too short (minimum is 7 characters)</li><li>Email is invalid</li><li>Password can't be blank</li><li>Password is too short (minimum is 4 characters)</li><li>Password is invalid</li><li>Password confirmation can't be blank</li>
These are the default messages of Active Record Validation . (ref: http://guides.rubyonrails.org/active_record_validations_callbacks.html )
This app was previously written in Rails 2 and later migrated to rails 3. I have changed the validates_presence_of commands to validates : password , :presence=>true etc in accordance with rails 3 .
In the view ( signup.html.erb) error_messages_for is rendering these msgs. It is deprecated from rails 3.
Can anyone tell me what needs to be used instead of error_messages_for in the view and all code needs to be changed correspondingly for getting the error msgs right..
Here's the code (not complete)
user.rb in app/model
class User < ActiveRecord::Base
has_many :excel_files # One user may have many excel files
has_one :user_access_validity# One user may have one license period
# Virtual attribute for the unencrypted password
attr_accessor :password
attr_accessible :login
attr_accessible :email
attr_accessible :password
attr_accessible :password_confirmation
attr_accessible :company
#changes of 'validates' in accordance with rails 3:
validates :login, :presence => true,
:length => { :within => 3..40},
:uniqueness => { :case_sensitive => false },
:format => { :with => /^([a-z_0-9\.]+)$/i },
:on => :create,
:if => :is_login_entered?
validates :email, :presence => true,
:length => { :within => 7..100},
:uniqueness => { :case_sensitive => false },
:format => {:with => /^([a-z]+((\.?)|(_?))[a-z0-9]+#(mindtree.com|rvce.edu.in))$/i},
:on => :create,
:if => :is_email_entered?
validates :company, :presence => true,
:format => { :with =>/(mindtree|RVCE)/i},
:format => { :with => /^([a-z]+)$/i },
:on => :create,
:if => :is_company_entered?
#validates_presence_of :login, :email, :company
on => :create, :if => :is_login_entered?
validates :password, :presence => true,
:length => { :within => 4..40 },
:confirmation => true,
:format => { :with => /^([a-z0-9#!#\$]+)$/i },
:on => :create,
:if => :password_required?
validates :password_confirmation, :presence => { :if => :password_required? }
#validates_presence_of :password_confirmation, :if => :password_required?
before_save :encrypt_password
.
.
.
In signup.html.erb
<font color=red>(Fields marked * are mandatory)</font><h3>Sign me up!</h3>
<br>
<span class='error'><%= error_messages_for (#user) %></span>
<%= form_for :user do |f| -%>
<p><label for="login"><span class='redcolor'>*</span>Login</label><br/>
<%= f.text_field :login %></p>
<p><label for="email"><span class='redcolor'>*</span>Email</label><br/>
<%= f.text_field :email %></p>
<p><label for="password"><span class='redcolor'>*</span>Password</label><br/>
<%= f.password_field :password %></p>
<p><label for="password_confirmation"><span class='redcolor'>*</span>Confirm Password</label><br/>
<%= f.password_field :password_confirmation %></p>
<p><label for="company"><span class='redcolor'>*</span>Company</label><br/>
<%= f.text_field :company %></p>
<p><%= submit_tag 'Sign up' %></p>
<% end -%>
Solution
Got the following code from http://www.rubydoc.info/github/edavis10/redmine/ApplicationHelper:error_messages_for which shud be added in application_helper.rb and corresponding change in html.erb file as <%= error_messages_for (#user) %>
Code:
def error_messages_for(*objects)
html = ""
objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("##{o}") : o}.compact
errors = objects.map {|o| o.errors.full_messages}.flatten
if errors.any?
html << "<div id='errorExplanation'><ul>\n"
errors.each do |error|
html << "<li>#{h error}</li>\n"
end
html << "</ul></div>\n"
end
html.html_safe
end
http://guides.rubyonrails.org/active_record_validations_callbacks.html
if you read this guide you will see that in you form you can use form.error_messages
Solution for the question is added below the question

Conditional Validations based on hidden_field_tag

In my form I use:
<%= hidden_field_tag :formtemplate, "newuser" %>
Based on this condition I want to validate in my model:
validates_format_of :email, :supervisor, :with => /^([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})$/i, :message => "Adresse überprüfen.", :if => :newuser_formtemplate?
Here is newuser_formtemplate?
def newuser_formtemplate?
newuser_formtemplate = "newuser"
newuser_formtemplate == "newuser"
end
How would I have to change this to make it validate based on the hidden__field_tag?
Use attr_accessor in your model
attr_accessor :formtemplate
def newuser_formtemplate?
newuser_formtemplate = self.formtemplate
newuser_formtemplate == "newuser"
end
If you are using form_for in your view then use form object
<%= form.hidden_field :formtemplate, "newuser" %>
How about using new_record? helper from activerecord instead of using hidden field
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-new_record-3F
validates_format_of :email, :supervisor, :with => /^([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})$/i, :message => "Adresse überprüfen.", :if => :new_record?

Updating user profile without having to enter the password and confirmation each time

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.

Using .build method to create through 1to1 association

I have a one to one relationship with a simple users model and a profiles model:
models/user
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_accessible :email, :password, :password_confirmation
has_one :profile, :dependent => :destroy
validates_presence_of :password, :on => :create
validates :password, :confirmation => true,
:length => { :within => 6..100 }
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => {:case_sensitive => false},
:length => { :within => 3..50 }
end
models/profile
# == Schema Information
#
# Table name: profiles
#
# id :integer not null, primary key
# weight :decimal(, )
# created_at :datetime
# updated_at :datetime
#
class Profile < ActiveRecord::Base
attr_accessible :weight
belongs_to :user
end
I am doing it this way because I would like users to be able to track weight over time as well as store other more static data like height in profiles.
However, my new and create methods don't seem to be working correctly. I on submit of the new action I get this error:
undefined method `build' for nil:NilClass
profile_controller
class ProfilesController < ApplicationController
def new
#profile = Profile.new if current_user
end
def create
#profile = current_user.profile.build(params[:profile])
if #profile.save
flash[:success] = "Profile Saved"
redirect_to root_path
else
render 'pages/home'
end
end
def destory
end
end
and profile view for new
<%= form_for #profile do |f| %>
<div class="field">
<%= f.text_field :weight %>
</div>
<div class="actions">
<%= f.submit "Submit" %>
</div>
<% end %>
Thanks in advance for any help you might be able to give. Noob here!
The build syntax for has_one association is different from has_many association.
Change your code as follows:
#profile = current_user.build_profile(params[:profile])
Reference: SO Answer

Resources