I have these models:
class Organisation < ActiveRecord::Base
  has_many    :people
  has_one     :address, :as         => :addressable,
                        :dependent  => :destroy
  accepts_nested_attributes_for :address, :allow_destroy => true
end
class Person < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :email, :organisation_id, :address_attributes
  belongs_to  :user
  belongs_to  :organisation
  has_one     :address, :as         => :addressable,
                        :dependent  => :destroy
  accepts_nested_attributes_for :address, :allow_destroy => true
  # These two methods seem to have no effect at all!
  validates_presence_of :organisation,  :unless => "address.present?"
  validates_associated  :address,       :unless => "organisation.present?"
end
class Address < ActiveRecord::Base
  belongs_to :addressable, :polymorphic => true
  validates_presence_of :line1, :line2, :city, :zip
end
...and these views:
_fields.html.erb:
<%= render 'shared/error_messages', :object => f.object %>
<fieldset>
<div class="left">
    <%= f.label :first_name %><br/>
    <%= f.text_field :first_name %>
</div>
<div>
    <%= f.label :last_name %><br/>
    <%= f.text_field :last_name %>
</div>
<div>
    <%= f.label :email %><br/>
    <%= f.text_field :email %>
</div>
<div>
    <%= f.label :organisation_id %><br/>
    <%= f.select(:organisation_id, current_user.organisation_names, {:include_blank => "--- None ---"}, :id => 'organisation_select') %>
</div>
</fieldset>
<%= f.fields_for :address do |address| %>
  <%= render 'shared/address', :f => address %>
<% end %>
_address.html.erb:
<fieldset id="address_fields">
<div>
    <%= f.label :line1 %>
    <%= f.text_field :line1 %>
</div>
<div>
    <%= f.label :line2 %>
    <%= f.text_field :line2 %>
</div>
<div>
    <%= f.label :zip %>
    <%= f.text_field :zip %>
</div>  
<div>
    <%= f.label :city %>
    <%= f.text_field :city %>
</div>  
</fieldset>
people_controller.rb:
def new
  puts params.inspect
  @person = Person.new(:organisation_id => params[:organisation_id])
  @person.build_address
  @title = "New person"
end
{"action"=>"new", "controller"=>"people"}
def edit
  puts params.inspect
  @title = @person.name
end
{"action"=>"edit", "id"=>"69", "controller"=>"people"}
def create
  puts params.inspect
  if params[:organisation_id]
    @person = current_user.organisations.build_person(params[:person])
  else
    @person = current_user.people.build(params[:person])
  end
  if @person.save
    flash[:success] = "Person created."
    redirect_to people_path
  else
    render :action => "new"
  end
end
{"commit"=>"Create", "action"=>"create", "person"=>{"last_name"=>"Doe", "organisation_id"=>"9", "email"=>"[email protected]", "first_name"=>"John", "address_attributes"=>{"city"=>"Chicago", "zip"=>"12345", "line2"=>"Apt 1", "line1"=>"1 Main Street"}}, "authenticity_token"=>"Jp3XVLbA3X1SOigPezYFfEol0FGjcMHRTy6jQeM1OuI=", "controller"=>"people", "utf8"=>"✓"}
Inside my Person model I need to make sure that only if a person's organisation_id is blank, that person's address fields have to be present.
I tried something like this:
validates :address, :presence => true, :if => "organisation_id.blank?"
But it's not working.
How can this be done?
Thanks for any help.
First of all, I want to be sure that you mean blank? rather than present?.  Typically, I see this:
validate :address, :presence_of => true, :if => 'organisation.present?'
Meaning, you only want to validate address if organisation is also present.
Regarding, :accepts_nested_attributes_for, are you using this feature by passing in nested form attributes, or some such thing?  I just want to make sure you absolutely need to use this functionality.  If you are not actually dealing with nested form attributes, you can implement cascading validation using:
validates_associated :address
If you do need to use :accepts_nested_attributes, be sure to check out the :reject_if parameter.  Basically, you can reject adding an attribute (and it's descendants) altogether if certain conditions apply:
accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :no_organisation
def no_organisation(attributes)
  attributes[:organisation_id].blank?
end
Now, if none of the above apply, let's take a look at your syntax:
It should work, :if/:unless take symbols, strings and procs.  You don't need to point to the foreign_key, but can simplify by pointing to:
:if => "organisation.blank?"
You have other validations in the Address model, correct? Is Address being validated when you don't want it to? Or is Address not being validated? I can help you test it out in the console if you can give me some additional details.
config.active_record.whitelist_attributes = false
I have a sample project as well. Let me know if you are interested.
Basic points:
Added the following to Person to ensure that either Org or Address are valid:
validates_presence_of :organisation, :unless => "address.present?"
validates_associated :address, :unless => "organisation.present?"
Added validation to Address to trigger errors when Org is not present:
validates_presence_of :line1, :line2, :city, :zip
I was able to produce the requirements you are seeking. Please look at the gist I created where I have a full console test plan.
I added a controller file to the previous gist.
Overview:
@person = current_user.people.build(params[:person])
params[:person][:organisation_id]
So you're if will never be true.I updated the gist with the necessary changes to the controller, the model and the form.
Overview:
accepts_nested_attribute, so in the :create, you only care about params[:person].  Additionally, in the render :new, you need to setup any instance variables that the partial will use.  This does NOT go back through the :new action.  The :new and :edit actions also need to be simplified.:reject_if argument because the Address fields are coming back to the :create action as :address_attributes => {:line1 => '', :line2 => '', etc}.  you only want to create the association if any have values.  Then your validates_presence_of for :organisation will work just fine.Your form needs to pass the organisation id to the controller, rather than the organisation names
It's all in the gist
Should be the final gist.
Overview:
Add the following to your edit action right after building the @person:
@person.build_address if @person.address.nil? This ensure that you have the address inputs, even if the @person.address does not exist. It doesn't exist, because of the :reject_if condition on accepts_nested_attributes
I DRYed up the :reject_if as follows. It's a little hacky, but has some utility:
accepts_nested_attributes_for :address, :allow_destroy => true, :reject_if => :attributes_blank?
def attributes_blank?(attrs)  
  attrs.except('id').values.all?(&:blank?)  
end  
a. attrs -> the result of params[:person][:address]
  b. .except('id') -> return all key-values except for 'id'
  c. .values -> return all values from a hash as an array
  d. .all? -> do all elements in the array satisfy the following check
  e. &:blank -> ruby shorthand for a block, like this: all?{ |v| v.blank? }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With