How to update model attribute from Model? - ruby-on-rails

I have a method to change user status inside the it's model, is it possible to use this do something like this inside the user model:
class User < ActiveRecord::Base
def confirm!
super
self.update_column(:status => "active")
end
end
I saw these two examples;
Rails update_attribute
how to update attributes in self model rails
couldn't quite get which one to go with!

It depends on whether or not you want any validations in the model to run. update_attribute will not run the validations, but update_attributes will. Here are a couple of examples.
Using update_attributes:
class User < ActiveRecord::Base
validates :email, presence: true
def confirm!
update_attributes(status: 'active')
end
end
The following will return false and will not update the record, because not email has been set:
user = User.new
user.confirm! # returns false
Using update_attribute:
class User < ActiveRecord::Base
validates :email, presence: true
def confirm!
update_attribute(:status, 'active')
end
end
The following will update status to active regardless of whether or not email has been set:
user = User.new
user.confirm! # returns true

Related

Rails 6 validate model only for specific controller

I've got User model with validation:
validates :experience_level, inclusion: { in: EXPERIENCE_LEVEL, allow_blank: true }
But one of the part of full registration is to update User's experience level. User can do this by inside of below controller:
module Users
class ExperienceLevelsController < SignupBaseController
def edit
authorize current_user
end
def update
authorize current_user
if current_user.update(user_experience_level_params)
redirect_to new_appropriateness_test_step_one_path,
else
render :edit
end
end
end
And for that endpoint I want to use
validates :experience_level, presence: true, inclusion: { in: EXPERIENCE_LEVEL }
I know I could use on: :update but in such case User will not be able to update e.g. password if it doesn't go through the experience update form first.
If you want to make the model state aware you can do it by explicitly passing information into the model:
class User < ApplicationRecord
attr_accessor :stage
validates :experience_level,
inclusion: { in: EXPERIENCE_LEVEL }
validates :experience_level, presence: true, if: :requires_experience_level?
def requires_experience_level?
stage == :add_experience_level
end
end
module Users
class ExperienceLevelsController < SignupBaseController
def edit
authorize current_user
end
def update
authorize current_user
if current_user.update(user_experience_level_params.merge(stage: :add_experience_level))
redirect_to new_appropriateness_test_step_one_path,
else
render :edit
end
end
end
end
There is also ActiveSupport::CurrentAttributes:
Abstract super class that provides a thread-isolated attributes
singleton, which resets automatically before and after each request.
This allows you to keep all the per-request attributes easily
available to the whole system.
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :stage
end
def update
authorize current_user
Current.stage = :add_experience_level
end
class User < ApplicationRecord
attribute_accessor :stage
validates :experience_level,
inclusion: { in: EXPERIENCE_LEVEL }
validates :experience_level, presence: true, if: :requires_experience_level?
def requires_experience_level?
Current.stage == :add_experience_level
end
end
Its really up to you if you want use it though as it can be considered harmful. If it quacks like a global...

How to validate Rails model based on a parameter?

I have User model, and need to validate phone number attribute based on the controller param.
class User < ActiveRecord::Base
validates_presence_of :phone_number
end
This validation should validate phone_number in the Create action.
Let's say the param I should check is
params[:phone_number]
you can use before_save validation, in User model you can write
before_save :validate_phone_number
private
def validate_phome_number
self.phone_number = /some regex/
end
In self.phone_number you will get controller params by default
validate :custom_validation, :on => :create
private
def custom_validation
//whatever you want to check
end
I have tried many ways to complete this task,
I used Inheritance - Created a sub class from the User class
Call a method in the model from the controller to set the attribute and bind that attribute with the validation
Use the context option
I guess the context option is the most reliable solution for this issue i faced. So here when i set the context as :interface the model validation will trigger only based on that value
Model - User.rb
class User < ActiveRecord::Base
validates_presence_of :phone_number, on: :interface
end
Controller - users_controller.rb
#user = User.new(user_params)
#save_result = false
if params[:invitation_token] == nil
save_result = #user.save(context: :interface)
else
save_result = #user.save
end
If you use multiple options in ON:
validates :terms_and_conditions, acceptance: {accept: true}, on: [:create, :interface], unless: :child
validates :privacy_policy, acceptance: {accept: true}, on: [:create, :interface], unless: :child

has_one validates_associated firing inconsistently despite 'on: :create'

I'm seeing really weird behaviour when checking valid? on a model that has a validates_associated :contact, on: :create. If I make two calls to valid? the first is true and the second is false.
This is a minimal version of the models, hopefully it's enough detail:
class Parent < ActiveRecord::Base
has_one :contact
accepts_nest_attributes_for :contact
validates_presence_of :contact
validates_associated :contact, on: :create
delegate :postcode,
:phone_number,
to: :contact
end
class Contact < ActiveRecord::Base
belongs_to :parent
belongs_to :country
validates_format_of :phone_number, if: :logged_in_australian?, allow_blank: true
validates_format_of :postcode, if: :logged_in_australian?, allow_blank: true
private
def logged_in_australian?
logged_in? && australian?
end
def logged_in?
current_user && current_user == user
end
def australian?
country && country.name == 'Australia'
end
end
The behaviour I'm seeing in the controller is an infinite redirect between two actions:
def dashboard
flash.keep if !parent.valid?
return redirect_to complete_signup_parent_path if !parent.valid?
# other stuff
end
def complete_signup
return redirect_to action: "dashboard" if parent.valid? #&& parent.valid?
# other stuff
end
If I uncomment the #&& parent.valid? it stops redirecting, which seems just insane.
The parents this happens for have an invalid phone_number, but the requirements around the phone_number changed after they signed up so we don't want to hassle them about it. So the desired behaviour is to get valid? to be true, and it is initially, it's just on subsequent calls it changes.
I've put in some debug statements and I can see that the validate context is :update for every call. So it shouldn't be running the validates_associated. These are also created parents, so there shouldn't be a :create or new_record? in play. Another debug statement proves that validations are running on contact, including the one for phone_number, but only the second time it gets called in an action.
I also put in a breakpoint and could see that parent.valid? returned true and then false, and also that if I break before valid? gets called and call parent.contact_detail and then parent.valid? then it returns false.
Why is the second call to parent.valid? validating contact even though it's only supposed to do that on: :create?
To tell without rest of code, it is hard. Are #parent and parent pointing towards the same object?
Results differ when #&& parent.valid? is uncommented which leads me to think that #parent.valid? && parent.valid? != #parent.valid?

rails how to validatie data params in method dose not updated, created

I want to validate 1 params in model method, but i can't found any fit answers , please show me the right way.
class User < ActiveRecord::Base
validate :username, presence: true, length: 4..5, unique: true
validate :email, presence: true, unique: true, format: {with: /\A[a-z0-9\.]+#([a-z]{1,10}\.){1,2}[a-z]{2,4}\z/}
def self.get_post(id)
# how to call validate id ???
validates :id, numericality: true
if id.valid?
# true code
else
# false code
end
end
def change_profile
# How to check validate user and email
username.valid?
email.valid?
# some_code....
end
end
Thanks all.
You cannot use validates there, you can do this instead
def self.get_post(id)
if id.is_a? Numeric
# true code
else
# false code
end
end
You can use active model for your customization, you can not check validation on field to filed, but you can perform with active model with number of fields as per your requirement
http://railscasts.com/episodes/219-active-model
class User
include ActiveModel::Validations
validates_with UserProfile
end
class UserProfile < ActiveModel::Validator
def validate(record)
if some_complex_logic
record.errors[:base] = "This record is invalid"
end
end
private
def some_complex_logic
# ...
end
end

validate inclusion not working on create

Okay I have quite a weird scenario that I do not know how to deal with so please bear with me as I try to explain it to you.
I have the following model.
class User < ActiveRecord::Base
Roles = { pending: 'pending_user', role2: 'role2', etc: 'etc' }
attr_accessible :role
validates :role, inclusion: {in: Roles.values}
before_create :add_pendng_role #Set user role to Roles[:pending]
end
Now the problem is when creating a record for the first time, this validation fails! For example in my controller I have the following code:
class UsersController < ActionController::Base
#user = User.new params[:user]
if #user.save # --------------- ALWAYS FAILS -------------------------------
#do something
else
#do something else
end
end
Now the reason I believe it fails is because a role is only added before_create which is called after the validations have passed. Now I know that I can't replace the before_create :add_role with before_validation :add_role because I think that it will add the role each time a validation is done. The reason I can't have that is because the user role will change in the application and I don't want to reset the role each time a validations are done on the user.
Any clues on how I could tackle this?
You could try:
before_validation :add_role, on: :create
Use *before_validation*, as explained in the rails callback guide
class User < ActiveRecord::Base
Roles = { pending: 'pending_user', role2: 'role2', etc: 'etc' }
attr_accessible :role
validates :role, inclusion: {in: Roles.values}
before_validation :add_pendng_role, on: :create #Set user role to Roles[:pending]
end
Looks like you'll be able to change before_create to before_validation if you use the :on argument:
before_validation :add_pendng_role, :on => :create

Resources