Rails update_attributes using has_secure_password - ruby-on-rails

I am working in a project where users can either have admin: true / false. The application will only let admin users login in to the system, the other users are just clients whose settings only admins can edit. This is some sort of commerce application.(Rails 3.2.13)
I would like to keep both concepts in the same table since in the future there will possibly be the option of logging in for non-admin users and interact with their profiles, so all the logic will be already implemented.
I've got this User resource:
ActiveRecord::Schema.define(:version => 20131204200554) do
create_table "users", :force => true do |t|
t.string "name"
t.string "surname"
t.boolean "admin"
t.string "password_digest"
t.string "email"
t.string "auth_token"
end
end
This is the user model:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :name, :surname,:email, :password, :password_confirmation
validates :name, presence:true, length: { maximum:50}
validates :first_surname, presence:true, length: { maximum:50}
VALID_EMAIL_REGEX= /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive:false}
validates :password, length:{minimum:6}
validates :password_confirmation, presence:true
before_save{ self.email.downcase! }
before_save :generate_auth_token
private
def generate_auth_token
self.auth_token=SecureRandom.urlsafe_base64
end
end
And I am trying to implement the functionality of User editing, however I only want to allow the editing of name, surname and email. Hence, I present only those fields in the form:
<%= form_for(#user) do |f| %>
<%= f.label :name,"Name" %>
<%= f.text_field :name %>
<%= f.label :surname,"Surname" %>
<%= f.text_field :surname %>
<%= f.label :email,"Email" %>
<%= f.text_field :email %>
<%= f.submit "Save" %>
I am trying to accomplish the goal with this code:
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
# Handle a successful update.
flash[:success]="User updated successfully"
redirect_to #user
else
flash[:danger]="Error updating user"
render 'edit'
end
end
My problem is that when trying to update_attributes I get not unexpectedly an error validating password/password_confirmation, but since I'm using has_secure_password, these fields do not exist in the database, only password_digest. I am thinking about the best option to accomplish all this:
Update #user object with new field values.
Reflect this change in User table.
Run the validations, i.e. email validation.
While using has_secure_password. These are the options so far:
Use of update_attribute(best so far).
1.1 Pro: updates the User table
1.2 Con: I have to update_attribute(field1) for each of the fields, so more lines of code
1.3 Con: Apparently this method no longer exists in Rails 4, problem in case an upgrade is desirable in the future.
1.4 Con: No validations
Use of #user.attributes=params[:user] in the controller method
2.1 Pro: Updates multiple fields at once.
2.2 Con: Does not update the User table.
User of update_attributes
3.1 Pro: Both multiple fields and table update
3.2 Con: Not working ( duh!)
Suggestions?

It looks like you want to validate the presence of password on create only (also notice more concise way to validate password confirmation):
validates :password, length:{minimum:6}, confirmation: true, on: :create
You can then use update_attributes
You may want to restrict what params can be submitted by using strong params:
Gem: https://github.com/rails/strong_parameters
also included in Rails 4 http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html

Related

Validation failed Class must exist

I have been (hours) trouble with associations in Rails. I found a lot of similar problems, but I couldn't apply for my case:
City's class:
class City < ApplicationRecord
has_many :users
end
User's class:
class User < ApplicationRecord
belongs_to :city
validates :name, presence: true, length: { maximum: 80 }
validates :city_id, presence: true
end
Users Controller:
def create
Rails.logger.debug user_params.inspect
#user = User.new(user_params)
if #user.save!
flash[:success] = "Works!"
redirect_to '/index'
else
render 'new'
end
end
def user_params
params.require(:user).permit(:name, :citys_id)
end
Users View:
<%= form_for(:user, url: '/user/new') do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :citys_id, "City" %>
<select name="city">
<% #city.all.each do |t| %>
<option value="<%= t.id %>"><%= t.city %></option>
<% end %>
</select>
end
Migrate:
class CreateUser < ActiveRecord::Migration[5.0]
def change
create_table :user do |t|
t.string :name, limit: 80, null: false
t.belongs_to :citys, null: false
t.timestamps
end
end
Message from console and browser:
ActiveRecord::RecordInvalid (Validation failed: City must exist):
Well, the problem is, the attributes from User's model that aren't FK they are accept by User.save method, and the FK attributes like citys_id are not. Then it gives me error message in browser saying that "Validation failed City must exist".
Thanks
Try the following:
belongs_to :city, optional: true
According to the new docs:
4.1.2.11 :optional
If you set the :optional option to true, then the presence of the
associated object won't be validated. By default, this option is set
to false.
This comes a bit late but this is how to turn off this by default in rails 5:
config/initializers/new_framework_defaults.rb
Rails.application.config.active_record.belongs_to_required_by_default = false
In case you don't want to add optional: true to all your belongs_to.
I hope this helps!
You need to add the following to the end of the belongs_to relationship statement:
optional: true
It is possible to set this on a global level so that it works in the same way as older versions of rails, but I would recommend taking the time to manually add it to the relationships that really need it as this will cause less pain in the future.
I found out a solution to the problem "Validation failed: Class must exist" and it's better than use:
belongs_to :city, optional: true
4.1.2.11 :optional
If you set the :optional option to true, then the presence of the associated object won't be validated. By default, this option is set to false.
cause you still make a validation in application level. I solve the problem making my own validation in create method and changing user_params method:
def create
#city = City.find(params[:city_id])
Rails.logger.debug user_params.inspect
#user = User.new(user_params)
#user.city_id = #city.id
if #user.save!
flash[:success] = "Works!"
redirect_to '/index'
else
render 'new'
end
end
def user_params
params.require(:user).permit(:name)
end
I didn't test this code, but it works in another project mine. I hope it can help others!
Rails 5
If you have a belongs_to relationship to :parent then you have to pass an existing parent object or create a new one then assign to children object.
belongs_to :city, required: false
params.require(:user).permit(:name, :citys_id)
It's a mistake, isn't it? (citys_id vs city_id)
Rails.application.config.active_record.belongs_to_required_by_default = false
This works because the Rails 5 has true by default
to disable you go under Initilizers then click on the New_frame-work and turn the true to false

Input validations on fields in ActiveAdmin

When I create a new form in ActiveAdmin, I want validations on my form input fields. But I can't find a related tutorial. I want some fields to accept only alphabets, some only digits , and some should be of specific length.
f.input :name, :label => "Title", input_html: { autofocus: true }
f.input :description
f.input :email
f.input :contact_number
f.input :contact_person
[Answer not only for ActiveAdmin, but for RoR in general]
You should do it in model.
• For digits only:
You want your :contact_number to be a digit, so your model (e.g. User) should look like this:
class User < ActiveRecord::Base
validates :contact_number, numericality: {only_integer: true}
end
• For min. 5 characters:
If description for example must be at least 5 characters it will be:
validates_length_of :description, minimum: 5
• For letters only:
validates_format_of :name, with: /^[-a-z]+$/
(details about reg. expressions --> Validate: Only letters, numbers and - )
Additional info:
If your form don't pass model validation it will return alert about wrong argument (which is accessible in flash[:alert] array).
More about it in:
http://guides.rubyonrails.org/active_record_basics.html#validations
You can have the validations defined in your corresponding Model class.
See the official documentation for Rails validation.
ActiveAdmin will pick it up when you try to create/edit/update objects of that model if you have Rails standard validations or even custom validations defined in your Model class.
For example, for your email validation, you can have this in your model:
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
Then, when you try to create/save an object through ActiveAdmin, it will show you error if the email is not in the correct format.
So, you have to define all of your validations (for all the fields that you want) in your model. That's it!
And, to display a list of all validation errors, you have to do:
form do |f|
f.semantic_errors *f.object.errors.keys
# ...
end
Update
Add these validations to your Model class:
validates_presence_of :description
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
validates :contact_number, :presence => {:message => 'hello world, bad operation!'},
:numericality => true,
:length => { :minimum => 10, :maximum => 15 }
These are Rails standard validations. You can add custom validations to your model too.
For example, if you want to add a custom validation for the username, you can define that like this:
validate :username_must_be_valid
And, then define the custom validator method username_must_be_valid in the same model class like this:
private
def username_must_be_valid
errors.add(:username, 'must be present') if username.blank? && provider.blank?
end

Rails Country_select can't save to database

I am having trouble integrating this select country plug in (https://github.com/scudco/country_select_test) into my test application. I installed it and put it into my database. It shows up when I type my id into the rails console but it always comes up as nil. Here is my view.haml:
.form-group
= f.input :country_code, autofocus: true, :class => "form-control"
.form-group
= f.input :address, autofocus: false, :class => "form-control"
Here is the migration I recently made:
class AddCountryCodeToUsers < ActiveRecord::Migration
def change
add_column :users, :country_code, :string
end
end
The output of my console:
updated_at: "2015-08-07 15:29:26", address: " ", country_code: nil>
I have nothing about the :country_code in my users.rb file. I used to have the assert country_code in their but it didn't change anything when I took it out. Thank you to everyone who helps me out. I'm still a noob at ruby on rails.
You probably miss to allow the :country_code param in your UsersController due to strong_parameters
In your controller you should have something like
def users_params
params.require(:user).permit(...)
end
You need to ensure, in the permit method, that :country_code is called too. For example:
params.require(:user).permit(:country_code, :email, :password, :password_confirmation)

Custom fields in form_for

Trying to create a profile form in which a user can add information about their hobbies to their user profile. But I get the following error when trying to do so.
NoMethodError in Users#edit_profile
undefined method `hobbies' for #<User:0x007ff1a8a1f198>
Does anyone know what the problem could be, or a potential solution? I'm new to rails but I was under the impression that 'text_field' was a safe bet to make any custom input work. Would installing the strong parameters gem help this out at all?
edit_profile.html.erb
<h2>Tell us about yourself</h2>
<%= form_for #user do |f| %>
<%= f.label :first_name %><br />
<%= f.text_field :first_name, autofocus: true %>
<%= f.label :last_name %><br />
<%= f.text_field :last_name %>
<%= f.label :hobbies %><br />
<%= f.text_field :hobbies %>
<div><%= f.submit "Update" %></div>
<% end %>
user_controller.rb
class UsersController < ApplicationController
before_filter :authenticate_user!
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
end
def edit
end
def update
#user = User.find(params[:id])
#user.update!(user_params)
redirect_to #user
end
def destroy
end
def edit_profile
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :hobbies)
end
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :first_name, :last_name, :email, :password, :password_confirmation, :remember_me, :hobbies
#validates :first_name, presence: true, length: { maximum: 50 }
#validates :last_name, presence: true, length: { maximum: 50 }
end
You don't mention it, but I will assume you're running Rails 4.x.
Rails 4.x introduced strong parameters, so in your controller you need to add a private method to set the allowed parameters and remove the attr_accessible from your model.
So in you case it will be:
def user_params
params.require(:first_name, :last_name).permit(:email, :password, :password_confirmation, :remember_me, :hobbies)
end
If you still have trouble to understand the concept, or came from a previous Rails version, take a look at this blog post.
Does anyone know what the problem could be, or a potential solution?
Sure - the problem is you don't have a hobbies attribute in your User model :)
Since you're new, I'll explain a bit about Rails after I answer the question. However, let me explain the value of creating the right attributes:
#app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :hobbies
end
This is what you'll need to create a single attribute for your User model. The attr_accessor directive is a Ruby method which sets a getter and setter in your User model
This will give Rails the ability to populate the hobbies attribute of your Model. However, the data will not be persistent, as it will only be set on a per-instance basis in the model, meaning it will be lost when you refresh the page etc
Using the code above should get your form working, regardless of whether you're using Rails 3 or 4.
Models
Rails is famously an MVC framework - which means it has 3 core components - models (builds data from the database), controllers (configure data for the view) & views (displays the data from the controller & model).
When you load a Rails application, you're sending a request, which will be routes to a particular controller action. This will then call data from your database, allowing you to manipulate it in your view.
Models, therefore have to be populated from your database. They do this by taking the various attributes you have in your datatables, and creating a series of getters and setters for them. These give you the ability to access the data within the attributes, or set new ones.
Your error occurs because you don't have the relevant attribute set in your datatable. You'll need to create a migration to add the hobbies attribute to your User model:
> $ rails g migration AddHobbiesToUser hobbies:string
> $ rake db:migrate
The migration should create something like:
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :users, :hobbies, :string
end
end

Rails & Devise: Email Confirmation text field?

I am using Rails 3.2 and Devise. I am wondering if Devise provides an "Email (address) Confirmation" field just like Password Confirmation for the user to type in a Password field and then type in a Password Confirmation field before the website sends out a confirmation email or processes the sign up registration. If not, must I add a email_confirmation and validate the User model (after rails g devise User)?
Thanks in advance
I haven't found if devise does what you ask (the question is referring to a second :email_confirmation field similar to :password_confirmation, 'confirmable' seems to be solely related to confirmation links). I had to do this as well and I figured out how to do it using builtin active record validation (rails 3.2.8, devise 2.1.2):
In your model add confirmation: true to your email validation and :email_confirmation to attr_accessible.
In your view add an :email_confirmation field.
As an example, in your model (not assuming other validations or attr_accessibles):
attr_accessible :email, :email_confirmation
validates :email, confirmation: true
which will use the builtin active record 'confirmation' validation (the same as for passwords). Finally, in your view(s) you'll need to add something like:
<div><%= f.label :email_confirmation %>
<%= f.email_field :email_confirmation %></div>
There may be a cleaner way but this worked for me. However, my only experience with devise so far has been when adding it to existing models, I haven't used it when it's generating the models for you (presumably it's mostly the same).
I was faced with the same issue.
Here's how I solved it
From the official RailsGuide
You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute whose name is the name of the field that has to be confirmed with "_confirmation" appended.
class Person < ApplicationRecord
validates :email, confirmation: true
end
In your view template you could use something like
<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>
This check is performed only if email_confirmation is not nil. To require confirmation, make sure to add a presence check for the confirmation attribute (we'll take a look at presence later on in this guide):
class Person < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: true
end
There is also a :case_sensitive option that you can use to define whether the confirmation constraint will be case sensitive or not. This option defaults to true.
class Person < ApplicationRecord
validates :email, confirmation: { case_sensitive: false }
end
The default error message for this helper is "doesn't match confirmation".
From my experimentation, using the official RailsGuide,
If you want to change the confirmation message to say, Email confirmation must be given correctly, then use add a message option this way:
class Person < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: { message: "must be given correctly" }
end
That's all.
I hope this helps
There are a few minor differences for newer versions of Rails compared to the original answer. Just figured I'd add this in case it helps anyone else.
Add an attr_accessor for email_confirmation to your model and validate the email field against the email_confirmation field. Set case_sensitive to false { case_sensitive: false } on the validation unless you have configured the email field to be case sensitive.
app/models/user.rb
class User < ApplicationRecord
attr_accessor :email_confirmation
validates :email, confirmation: { case_sensitive: false }
end
Permit email_confirmation on the sign up params.
app/controllers/users/registrations_controller.rb
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:email_confirmation])
end
Add the email_confirmation field to your sign up form.
app/views/devise/registrations/new.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render 'devise/shared/error_messages', resource: resource %>
<div class="field">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, autocomplete: 'email', required: true %>
</div>
<div class="field">
<%= f.label :email_confirmation %>
<%= f.email_field :email_confirmation, required: true %>
</div>
<!-- other fields -->
<% end %>
Assuming you have the gem installed, when you perform this function :
rails generate devise:install
It will generate your migration file that will contain your email_confirmation stuff that you will need to go in the direction you're referring to. All you have to do is not comment it out. Otherwise, I comment out all the other stuff that I don't need.
The module you are referring to is called confirmable
Assuming you havn't found this yet, this link will explain the rest :
How do I set up email confirmation with Devise?

Resources