has_one validates_associated firing inconsistently despite 'on: :create' - ruby-on-rails

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?

Related

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

Belongs To User association

I just want to show the username who created the events. But when I try it says
undefined method `user_name' for nil:NilClass
This is my event.rb
class Event < ActiveRecord::Base
belongs_to :user
validates :name, presence: true
validates :description, presence: true, length: {minimum: 5}
end
And this is my user.rb
class User < ActiveRecord::Base
has_secure_password
has_many :events
end
And I am trying to show the user name in html.erb file like this.
<%= event.user.user_name %>
But I am getting this error.
So this is my create method in events_controller
def create
#event = current_user.events.new(event_params)
respond_to do |format|
if #event.save
format.html { redirect_to #event, notice: 'Event was successfully created.' }
else
format.html { render :new }
end
end
end
So what should I do for showing username in that page.
Thank You
Ok so here's the problem:
You want to show the event's user's name in index page but you can't be sure all events have one user associated with them. To avoid it you could use.
<%= event.user.try(:user_name) || 'No user associated' %>
Good luck!
Although the answer will get your app to work, it won't fix your problem.
The core issue is that your Event doesn't have an associated User object. This would not be a problem, except it seems from your code that you require an Event to have an associated User (current_user.events.new etc)...
You need the following:
#app/models/event.rb
class Event < ActiveRecord::Base
belongs_to :user
validates :user, presence: true #-> always makes sure user exists
delegate :user_name, to: :user #-> event.user_name
end
--
If you use the above code, you'll be able to call #event.user_name (solving the law of demeter with delegate). You'll also benefit from using validates presence to ensure the :user association exists on create & update.
This will allow you to rely on the #event.user object, which - to me - is far better than having to say "No user associated" in your app:
#view
<%= #event.user_name if #event.user %>

How to update model attribute from Model?

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

How can I add a validation error in before_save association callbacks

I have two models: The Discount has and belongs to many Businsses.
I want to validate that a Discount always have at least one business, together with another condition (for example active?). I tried the following:
class Discount < ActiveRecord::Base
has_and_belongs_to_many :businesses,
before_remove: :validate_publish_status
def validate_publish_status(*arg)
if active? && businesses.count == 0
errors[:active] << 'discount with no business'
end
end
end
However this does not work (no validation errors raised) and I realized that this is probably because it is only a callback, not a validation. How can I code it so I can use the errors like I do I custom validation?
The controller action I have (for ajax):
def remove
#business = Business.find(params[:business_id])
if #business.in? #discount.businesses
#discount.businesses.delete(#business)
end
render json: #business.as_json(only: [:id, :type, :name, :address],
methods: [:city_name, :country_name]).
merge(paths: paths_for(#discount, #business))
rescue ActiveRecord::RecordInvalid # even tried the generic Exception
respond_to do |f|
f.json { render json: {error: $!.message}, status: 403 }
end
end
Could be your syntax on the before_remove callback or what's happening in the validation method itself. You also might want to add some debug code in the callback method to see if it's even making in there.
*Note that the transaction will only be stopped if an exception is raised in the callback method. Since this is the case, you'll probably want to handle the exception logic in your controller to re-render the action:
class Discount < ActiveRecord::Base
has_and_belongs_to_many :businesses, :before_remove => :validate_publish_status
def validate_publish_status(*args)
if yyy? && businesses.count == 0
errors.add(:yyy,'discount with no business')
raise "Unable to remove business."
end
end
end
controller gist:
def update
#company.find(params[:id])
if #company.update_attributes(params[:company])
...
else
render :action => 'edit'
end
rescue
render :action=>'edit'
end
Note the Association Callback Documentation.
You can use the validates method with :presence => true for this.
class Discount < ActiveRecord::Base
has_and_belongs_to_many :businesses
validates :businesses, :presence => true
end
Using :presence => true on an association validator will ensure that at least one member of the association exists.

Rails updating attributes of a User Model from OrdersController

This my code:
class OrdersController
def create
#order = Order.new(params[:order])
if #order.purchase
work = GATEWAY.store(credit_card, options)
result = work.params['billingid']
current_user.update_attributes(:billing_id => result)
end
end
end
billingid is returned by running GATEWAY.store(credit_card, options)
I am trying to save this returned billingid into :billing_id column in User Model. Is it not possible to update attribute of User model from a that is not UsersController?
Simply put, is it not possible to update an attribute of model #1 from a controller of model #2?
Thanks
UPDATE:
With the help of the men below, I was able to verify two things:
1. result = work.params ['billingid'] returns string
2. That I am able to save into a different model from any controller
However, even though I have attr_accessible :billing_id I am still unable to save the result into billing_id column of User table. I was successful in saving the result in a store_name column of a Store table, so I don't know what it is about User model that is preventing me from saving.
I ran,
#mystore = Store.find(current_user)
#mystore.store_name = result
#mystore.save
and it was successful. But,
#thisuser = User.find(current_user)
#thisuser.billing_id = result
#thisuser.save
This fails even though attr_accessible is set correctly. What else could prevent from saving certain attributes other than attr_accessible? Thanks everyone!
UPDATE 2: User Model
require 'digest'
class User < ActiveRecord::Base
has_one :store
has_many :products
attr_accessor :password
# attr_accessible was commented out completely just to check as well. Neither worked
attr_accessible :name, :email, :password, :password_confirmation, :username, :billing_id
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 }
username_regex = /^([a-zA-Z0-9]{1,15})$/
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
end
UPDATE FINAL: SOLUTION
using #thisusers.errors, I was able to find out that it was trying to validate the presence of password during this request. Once I commented it out, it saved without an issue. I am unsure why this is happening, but I will take it from here. Thanks everyone esp. dmarkow!
There should be no issue updating any number of models from a controller.
Make sure that work.params['billingid'] actually contains a value.
Your User model may have some attributes marked as attr_accessible (since you have current_user, I assume you have authentication, and this often means needing to protect your model's attributes by default). If this is the case, that means that only those attributes can be changed by mass assignment (e.g. using update_attributes). Either add billing_id to the list of attributes that are attr_accessible, or don't use mass assignment. (Instead, you would just do current_user.billing_id = result and then current_user.save)
Edit: The problem wound up being a validation error on the User model. Always make sure to check the user.errors when user.save returns false.

Resources