get parent values in child model - ruby-on-rails

I have a model called RsvpRegistrations with
belongs_to :rsvp
I need to use values from the parent 'rsvp' object in my validations such as
validates_presence_of :phone if self.rsvp.phone
(Rsvp.phone is boolean)
But this doesn't work. The error I get is undefined method `rsvp'. How can I access the parent object and its values?
Once I get it working, I have other similar validations to run, so I'm thinking I need to grab the parent 'rsvp' one time and then reference it in my other validations.
Thanks in advance.

validates_presence_of :phone, :if => Proc.new { |obj| obj.rsvp.phone? }
More options here

If you have multiple validations that all reference RSVP, it may be more efficient to create a custom validation method:
# app/models/rsvp_registration.rb
def RsvpRegistration
def validate
rsvp = self.rsvp
errors.add(:rsvp, 'Phone is missing') unless rsvp.phone?
errors.add(:rsvp, 'Other messages') if condition
end
end

Related

Rails - extend attributes method

I have a group of input, for which I expect a large amount data (list of objects), so I want this input on create/update action to be wrapped inside of ActiveRecord transaction.
There is model Student, which has_one Account.
ACCOUNT_FIELDS=[:name, :surname, :email, :phone, :activated]
has_one :account, :as => :account_holder, autosave: true, :dependent => :destroy
validates_associated_extended :account
ACCOUNT_FIELDS.each do |action|
define_method action do
get_or_build_account.send(action)
end
setter_action = action.to_s + "="
define_method setter_action do |arg|
get_or_build_account.send(setter_action, arg)
end
end
here I made a reader/writer methods, so #student.name will return related data from account, also I can assign it through #student thanks to autosave.
Issue: as I said, I want it to be wrapped inside of transaction, so in my controller I don't save anything. Each student is assigned like this
student.attributes = #...data
Where student later on passed to transaction block.
But! For this specific model I want to student.attributes also return fields from ACCOUNT_FIELDS.
Normally it works with student.account_attributes but as I said, later student is processed in transaction, and it is made with module, which I want to be reusable for some other models (which doesn't need this logic).
So rather than modifying my module code with some conditions, I want instance of this model to return needed account fields when just called self.attributes
#student.attributes #=> :school_id => 1, :name => "John"...
where name is self.name from self.account.name
Try this:
def attributes
new_attributes={}
ACCOUNT_FIELDS.each do |field|
new_attributes[field]=self.send(field)
end
super.merge(new_attributes)
end

validate and update single attribute rails

I have the following in my user model
attr_accessible :avatar, :email
validates_presence_of :email
has_attached_file :avatar # paperclip
validates_attachment_size :avatar,
:less_than => 1.megabyte,
:message => 'Image cannot be larger than 1MB in size',
:if => Proc.new { |imports| !imports.avatar_file_name.blank? }
in one of my controllers, I ONLY want to update and validate the avatar field without updating and validating email.
How can I do this?
for example (this won't work)
if #user.update_attributes(params[:user])
# do something...
end
I also tried with update_attribute('avatar', params[:user][:avatar]), but that would skip the validations for avatar field as well.
You could validate the attribute by hand and use update_attribute, that skips validation. If you add this to your User:
def self.valid_attribute?(attr, value)
mock = self.new(attr => value)
if mock.valid?
true
else
!mock.errors.has_key?(attr)
end
end
And then update the attribute thusly:
if(!User.valid_attribute?('avatar', params[:user][:avatar])
# Complain or whatever.
end
#user.update_attribute('avatar', params[:user][:avatar])
You should get your single attribute updated while only (manually) validating that attribute.
If you look at how Milan Novota's valid_attribute? works, you'll see that it performs the validations and then checks to see if the specific attr had issues; it doesn't matter if any of the other validations failed as valid_attribute? only looks at the validation failures for the attribute that you're interested in.
If you're going to be doing a lot of this stuff then you could add a method to User:
def update_just_this_one(attr, value)
raise "Bad #{attr}" if(!User.valid_attribute?(attr, value))
self.update_attribute(attr, value)
end
and use that to update your single attribute.
A condition?
validates_presence_of :email, :if => :email_changed?
Have you tried putting a condition on the validates_presence_of :email ?
http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000083
Configuration options:
if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
unless - Specifies a method, proc or string to call to determine if the validation should not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should return or evaluate to a true or false value.
I am assuming you need this, because you have a multi-step wizard, where you first upload the avatar and the e-mail is filled in later.
To my knowledge, with your validations as they are, I see no good working solution. Either you validate all, or you update the avatar without validations. If it would be a simple attribute, you could check if the new value passes the validation seperately, and then update the model without validations (e.g. using update_attribute).
I can suggest two possible alternative approaches:
either you make sure that the e-mail is always entered first, which I believe is not a bad solution. And then, with each save, the validation is met.
otherwise, change the validation. Why would you declare a validation on a model, if there are records in the database that do not meet the validation? That is very counter-intuitive.
So I would propose something like this:
validate :presence_of_email_after_upload_avatar
def presence_of_email_after_upload_avatar
# write some test, when the email should be present
if avatar.present?
errors.add(:email, "Email is required") unless email.present?
end
end
Hope this helps.
Here is my solution.
It keeps the same behaviour than .valid? method, witch returns true or false, and add errors on the model on witch it was called.
class MyModel < ActiveRecord::Base
def valid_attributes?(attributes)
mock = self.class.new(self.attributes)
mock.valid?
mock.errors.to_hash.select { |attribute| attributes.include? attribute }.each do |error_key, error_messages|
error_messages.each do |error_message|
self.errors.add(error_key, error_message)
end
end
self.errors.to_hash.empty?
end
end
> my_model.valid_attributes? [:first_name, :email] # => returns true if first_name and email is valid, returns false if at least one is not valid
> my_modal.errors.messages # => now contain errors of the previous validation
{'first_name' => ["can't be blank"]}

Rails validation context

I need help with my ActiveRecord model. I have context based validations (mis)using the build-in context options for validations:
validates :foo, :on => :bar, :presence => true
model = Model.new
model.foo = nil
model.valid? # => true
model.save # works as expected
model.valid?(:bar) # => false
model.save(:context => :bar) # fails and returns false
But using my model in a accepts_nested_attributes_for :model and calling parent.save fails (the validation gets called and returns false), any suggestions or solutions?
Still no answer? To explain more about my problem: I have a model called Form which has many Fields. Users should see validation errors on submit, but the form should be saved anyway (with and without errors). There are different types of Fields, each with global validations (to ensure database consistency) and its own specific user-defined validations (to validate user-entered data). So my Fields look someway like that:
# Global validations, to ensure database consistency
# If this validations fail, the record should not be saved!
validates_associated :form, :on => :global
...
# Specific user-defined validations
# If this validations fail, the record should be saved but marked as invalid. (Which is done by a before_save filter btw.)
def validate
validations.each do |validation| # Array of `ActiveModel::Validations`, defined by the user and stored in a hash in the database
validation.new(:on => :specific).validate(self)
end
end
In my controller:
# def create
# ...
form.attributes = params[:form]
form.save!(:global)
form.save(:specific)
Is something similar possible in Rails using the built-in functionality? Btw this not my actual code, which is quite complicated. But I hope, you guys will get the idea.
Try conditional validations
class Customer
attr_accessor :managing
validates_presence_of :first_name
validates_presence_of :last_name
with_options :unless => :managing do |o|
o.validates_inclusion_of :city, :in=> ["San Diego","Rochester"]
o.validates_length_of :biography, :minimum => 100
end
end
#customer.managing = true
#customer.attributes = params[:customer]
#customer.save
"Ability to specify multiple contexts when defining a validation" was introduced in Rails 4.1 - check validate method, :on options description
Only for Rails 5+:
You are looking for
with_options on: :custom_event do
validates :foo, presence: true
validates :baz, inclusion: { in: ['b', 'c'] }
end
To validate or save use
model = YourModel.new
# Either
model.valid?(:custom_event)
# Or
model.save(context: :custom_event)
Change has_nested_attributes_for :model to accepts_nested_attributes_for :models.
Hope this helps.
Good luck.

Validate model for certain action

I need to validate a model only for a certain action (:create). I know this is not a good tactic, but i just need to do this in my case.
I tried using something like :
validate :check_gold, :if => :create
or
validate :check_gold, :on => :create
But i get errors. The problem is that i cannot have my custom check_gold validation execute on edit, but only on create (since checking gold has to be done, only when alliance is created, not edited).
Thanx for reading :)
I'm appending some actual code :
attr_accessor :required_gold, :has_alliance
validate :check_gold
validate :check_has_alliance
This is the Alliance model. :required_gold and :has_alliance are both set in the controller(they are virtual attributes, because i need info from the controller). Now, the actual validators are:
def check_gold
self.errors.add(:you_need, "100 gold to create your alliance!") if required_gold < GOLD_NEEDED_TO_CREATE_ALLIANCE
end
def check_has_alliance
self.errors.add(:you_already, "have an alliance and you cannot create another one !") if has_alliance == true
end
This works great for create, but i want to restrict it to create alone and not edit or the other actions of the scaffold.
All ActiveRecord validators have a :on option.
validates_numericality_of :value, :on => :create
Use the validate_on_create callback instead of validate:
validate_on_create :check_gold
validate_on_create :check_has_alliance
Edit:
If you use validates_each you can use the standard options available for a validator declaration.
validates_each :required_gold, :has_alliance, :on => :create do |r, attr, value|
r.check_gold if attr == :required_gold
r.check_has_alliance if attr == :has_alliance
end
Like Sam said, you need a before_create callback. Callbacks basically mean 'execute this method whenever this action is triggered'. (More about callbacks here : http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html).
This is what you want in your model:
before_create :check_gold
# other methods go here
private # validations don't need to be called outside the model
def check_gold
# do your validation magic here
end
The above method is the simplest to do what you want, but FYI there's also a way to use a before_save callback to execute additional actions on creation:
before_save :check_gold_levels
# other methods
private
def check_gold_levels
initialize_gold_level if new? # this will be done only on creation (i.e. if this model's instance hasn't been persisted in the database yet)
verify_gold_level # this happens on every save
end
For more info on 'new?' see http://api.rubyonrails.org/classes/ActiveResource/Base.html#method-i-new%3F
You need to look into callbacks. Someone once told me this and I didn't understand what they meant. Just do a search for rails callbacks and you will get the picture.
In your model you need to do a callback. The callback you need is before_create and then before a object is created you will be able to do some logic for check for errors.
model.rb
before_create :check_gold_validation
def check_gold_validation
validate :check_gold
end
def check_gold
errors.add_to_base "Some Error" if self.some_condition?
end

Rails form validation conditional bypass

I have a rails model that validates uniqueness of 2 form values. If these 2 values aren't unique the validation errors are shows and the "submit" button is changed to "resubmit". I want to allow a user to click the "resubmit" button and bypass the model validation. I want to do something like this from the rails validation documentation:
validates_uniqueness_of :value, :unless => Proc.new { |user| user.signup_step <= 2 }
but I don't have a a value in my model that I can check for...just the params that have the "Resubmit" value.
Any ideas on how to do this?
In my opinion this is the best way to do it:
class FooBar < ActiveRecord::Base
validates_uniqueness_of :foo, :bar, :unless => :force_submit
attr_accessor :force_submit
end
then in your view, make sure you name the submit tag like
<%= submit_tag 'Resubmit', :name => 'foo_bar[force_submit]' %>
this way, all the logic is in the model, controller code will stay the same.
Try this:
Rails 2: Model.save(false)
Rails 3: Model.save(:validate => false)
It bypasses validations (all of them though).
Not positive about this, but you could try to add an attr_accessor to your model to hold whether or not the form has been submited once before.
just add
attr_accessor :submitted
to your model and check for it in your validations.
You can just look at the submit button to determine whether you want to perform the validations.
def form_method
case params[:submit]
when "Submit"
'Do your validation here'
when "Resubmit"
'Do not call validation routine'
end
end

Resources