How can I run validations on derived values for new objects? - ruby-on-rails

I have a model that has several attributes that are provided at creation. The model also has some additional attributes that are derived from the provided attributes, which I also want to calculate at creation. More problematically, I want to be able to run validations on these derived values (since there are inputs that are valid on their own that lead to invalid derived values).
The problem is that when I do this:
class MyClass < ActiveRecord::Base
attr_accessible :given1, :given2, :derived
before_validation :derivation
validates_uniqueness_of :derived
def derivation
self.derived = self.given1 + self.given2
end
end
MyClass.new(:given1 => aNumber, :given2 => otherNumber)
I always get errors saying I can't add nil to nil. Apparently self.attribute is nil until farther into the validation & creation process.
Obviously I could set my derived values in a later stage, and add a custom validation that works on the given attributes, but that would entail doing the derivation twice, which wouldn't be very DRY.
Is there some other way to get at assigned but not yet validated attributes in the before_validates stage?
Edit: To clarify, I want to call MyClass.new(:given1 => aNumber, :given2 => otherNumber) and have the derived value calculated before the validations check, so that the validations check as if I had called MyClass.new(:given1 => aNumber, :given2 => otherNumber, :derived => aNumber + otherNumber). The problem is that I can't seem to access the passed-in values for :given1 and :given2 in a before_validations method.

I wrote my own snippet of code that looks like this:
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name
validates :email, uniqueness: true
before_validation :derivation
def derivation
self.email = self.first_name + self.last_name
end
end
Running the following yielded no errors:
» u = User.new first_name: "leo", last_name: "correa"
=> #<User:0x007ff62dd8ace0> {
:id => nil,
:first_name => "leo",
:last_name => "correa",
:email => nil,
:created_at => nil,
:updated_at => nil,
}
» u.valid?
User Exists (0.9ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'leocorrea' LIMIT 1
=> true
Running u.save saved the record successfully and upon repeating the User.new and saving that new record it returned with ROLLBACK because email was already used.
In any case, make sure you are assigning whatever variables you are using to the given1, given2 and whatever the result is make sure is not giving you false either because it will cancel the before_validate callback and the record won't save.

Related

In RoR, how do I initialize fields in my model based on a field in my model that has no underlying database column?

I’m using Rails 4.2.7. I have an attribute in my model that doesn’t have a database field underneath it
attr_accessor :division
This gets initialized when I create a new object.
my_object = MyObject.new(:name => name,
:age => get_age(data_hash),
:overall_rank => overall_rank,
:city => city,
:state => state,
:country => country,
:age_group_rank => age_group_rank,
:gender_rank => gender_rank,
:division => division)
What I would like is when this field gets set (if it is not nil), for two other fields that do have mappings in the database to get set. The other fields would be substrings of the “division” field. Where do I put that logic?
I'd probably drop the attr_accessor :division and do it by hand with:
def division=(d)
# Break up `d` as needed and assign the parts to the
# desired real attributes.
end
def division
# Combine the broken out attributes as needed and
# return the combined string.
end
With those two methods in place, the following will all call division=:
MyObject.new(:division => '...')
MyObject.create(:division => '...')
o = MyObject.find(...); o.update(:division => '...')
o = MyObject.find(...); o.division = '...'
so the division and the broken out attributes will always agree with each other.
If you try to use one of the lifecycle hooks (such as after_initialize) then things can get out of sync. Suppose division has the form 'a.b' and the broken out attributes are a and b and suppose that you're using one of the ActiveRecord hooks to break up division. Then saying:
o.division = 'x.y'
should give you o.a == 'x' but it won't because the hook won't have executed yet. Similarly, if you start with o.division == 'a.b' then
o.a = 'x'
won't give you o.division == 'x.b' so the attributes will have fallen out of sync again.
I see couple of options here
You can add it in your controller as follows
def create
if params[:example][:division]
# Set those params here
end
end
Or you can use before_save In your model
before_save :do_something
def do_something
if division
# Here!
end
end

When does validate method get called in Rails?

I have a model StudentProductRelationship. I am adding a custom validator
validate :validate_primary_product , :if => "!primary_product"
The method is
def validate_primary_tag
unless StudentProductRelationship.exists?(:primary_product => true, :student_id => student_id)
errors.add(:base,"There is no primary product associated to product")
else
end
end
primary_product is a boolean field. I want to validate presence of at least one true primary_product for student_id. The problem is if I have an StudentProductRelationship object say spr with primary_product = true. If I do spr.update_attributes(primary_product: false). The validation does not raise an error because StudentProductRelationship.exists?(:primary_product => true, :student_id => student_id) exists beacuse spr still exists in db with primary_product = true. How do i surpass this?
Doesn't validates_presence_of :primary_product, scope: :student_id work for your?

Ruby on Rails validation - If model is invalid, then?

I have an EmailContact with validation, like so:
class EmailContact < ActiveRecord::Base
validates :email, :presence => true, :email => {:message => I18n.t('validations.errors.models.user.invalid_email')},
:mx => {:message => I18n.t('validations.errors.models.user.invalid_mx')}
end
Here I am validating EmailContact.email.
Then I have a PhoneContact with no validation:
class PhoneContact < ActiveRecord::Base
end
I want to write something like this:
email_contact = EmailContact.create(params)
if email_contact.invalid?
phone_contact = PhoneContact.create(params)
end
Basically, if the email_contact can't be created due to validation errors, I should then create a phone_contact. How is that possible?
This is what I've tried:
contact = EmailContact.create(:email => 'a')
(0.3ms) BEGIN
(0.4ms) ROLLBACK
ArgumentError: cannot interpret as DNS name: nil
contact.invalid?
NoMethodError: undefined method `invalid?' for nil:NilClass
contact just returns nil in this case...
EDIT
It may be that this question needs to go a different direction. Just FYI:
email_contact = EmailContact.new(:email => 'a')
email_contact.valid?
ArgumentError: cannot interpret as DNS name: nil
email_contact.valid? returns an error instead of returning false as I would expect. I am using the valid_email gem to do my validation on the model.
Using invalid? method, you could do something like this :
email_contact = EmailContact.new(params)
if email_contact.invalid?
phone_contact = PhoneContact.create(params)
else
email_contact.save
end
In your case, you used email_contact = EmailContact.create(params).
email_contact would be set to nil if creation fails and you won't be able to call invalid? on a nil object.
Check for invalid? before creating the record in database.
You should use #new and then call #save directly.
email_contact = EmailContact.new(params)
if !email_contact.save
phone_contact = PhoneContact.create(params)
end

In Rails, how do I limit which attributes can be updated, without preventing them from being created?

I have a situation where an attribute can be created through a JSON API. But once it is created, I want to prevent it from ever being updated.
This constraint causes my first solution, which is using attr_accessible, to be insufficient. Is there a nice way to handle this type of situation in rails, or do I have to perform a manual check in the update method?
You can use attr_readonly, this will allow the value to be set on creation, but ignored on update.
Example:
class User < ActiveRecord::Base
attr_accessible :name
attr_readonly :name
end
> User.create(name: "lorem")
> u = User.first
=> #<User id: 1, name: "lorem">
> u.name = "ipsum"
=> "ipsum"
> u.save
=> true
> User.first.name
=> "lorem"
There is not a nice way to do that as far as I know, you have to write a custom filter
before_update :prevent_attributes_update
def prevent_attribute_updates
%w(attr1, attr2).each do |a|
send("#{attr1}=", send("#{attr1}_was")) unless self.send("#{attr1}_was").blank?
end
end

Is it possible to determine whether an ActiveRecord attribute has been set, other than to the default?

We have an ActiveRecord model whose columns have some default values. It also has a validation condition such that, if the 'profile' property is set to a different value, then the default values are invalid.
What I'd like is to be able to determine whether the attributes have been set since they were set to the default so that I can reset them to new, valid values before validation.
Is this possible?
UPDATE: It's not clear what I mean, so I should give code examples.
The table is defined like this:
t.column :profile, :string
t.column :contact_by, :string, :default => "phone", :null => false
The validation is like this:
validate :validate_contact_by_for_profile1
def validate_contact_by
if (profile == "profile1") && (contact_by != "email")
errors.add(:contact_by, " must be 'email' for users in profile1")
end
end
So any time we do this:
u = User.new
u.profile => profile1
We end up with u being invalid. What I want to end up with is that the user's contact_by defaults to "phone", but if their profile is set to profile1, then it changes to "email", unless it has been set to something in the meantime. (Ideally this includes setting it to "phone")
EDITED ANSWER:
ok, don't know if I understood, but I'll try :P
you can writing a method to ovveride the profile= setter:
def profile=(value)
self.contact_by = 'email' if value == 'profile1'
super
end
this way works as you expect:
> n = YourModel.new
=> #<YourModel id: nil, profile: nil, contact_by: "phone", ...>
> n.profile = 'profile2'
=> "profile2"
> n.contact_by
=> "phone"
> n.profile = 'profile1'
=> "profile1"
> n.contact_by
=> "email"
as you can see, this way you get want you want. then you can do whatever validation you need .
hope this time helped ;-)
OLD ANSWER:
generally, you set default values during db migration. so when you try to save some data, the ActiveRecord model has blank data, then when saved on db, it gets default values.
according to this, you can play with validations in the model using something like this:
validates_presence_of :some_field, :if => Proc.new {|m| p.profile != <default value>}
alternatively, you can write a custom validation code, as private method, that will be called from validate:
validate :your_custom_validation_method, :if => Proc.new {|m| p.profile != <default value>}
btw I suggest you to look at ActiveRecord validations docs: http://guides.rails.info/active_record_validations_callbacks.html

Resources