Nested Attributes: unwanted validation despite of reject_if : All_blank - ruby-on-rails

I am new to rails so any advise is greatly appreciated.
I have a class Entry with nested attributes Addresses,
/app/models/entry.rb
class Entry < ActiveRecord::Base
has_many :addresses, :dependent => :destroy
accepts_nested_attributes_for :addresses,
:allow_destroy => true,
:reject_if => :all_blank
end
with class Addresses like this
/app/models/address.rb
class Address < ActiveRecord::Base
belongs_to :entry
validates :zip, :presence => true
end
And in the nested form I have
/app/view/entries/_form.html.slim
= simple_form_for(#entry) do |f|
= f.error_notification
- #entry.addresses.build
.form-inputs
= f.simple_fields_for :addresses do |address|
= render 'address_form', :f => address
The idea is that when the form is rendered, the 'build' will create a empty 'address' in addition to the current addresses listed in database. When the changes are saved, if the new address created is still empty, it will get rejected and not saved to the database.
However the validation in the address.rb is doing the validation before the saving, hence the user cannot proceed with the saving action. Is there anything I left out?

You might like to try explicitly naming the attributes in your address model that get checked before a new, empty one is created. Something like this:
# in app/models/entry.rb
accepts_nested_attributes_for :addresses, reject_if: lambda {|attributes| nested_address_is_empty?(attributes) }
private
def self.nested_address_is_empty?(attrs)
attrs['line_1'].blank? && attrs['line_2'].blank? && attrs['zip'].blank?
end

Try passing false as an argument to save to skip validations.

Related

accepts_nested_attributes_for allow_destroy doesn't work with scoped validation

I am trying to puzzle out why the accepts_nested_attributes_for will not destroy a record when it receives a hash like this:
{project_parcels_attributes"=>[{"parcel_id"=>"680060", "_destroy"=>"1"}, {"parcel_id"=>"680088"}]}
The context of use is as follows. There is a Project model:
class Project < ActiveRecord::Base
has_many :project_parcels
accepts_nested_attributes_for :project_parcels, allow_destroy: true
end
class ProjectParcels < ActiveRecord::Base
belongs_to :project
belongs_to :parcel
validates :parcel_id, uniqueness: {scope: :project_id}
end
Then I call it from a form like this:
#project.update_attributes({"project_parcels_attributes"=>[{"parcel_id"=>"680060", "_destroy"=>"1"}, {"parcel_id"=>"680088"}]})
Yet, it doesn't work. The validation stops the record from being destroyed. But when I remove validation, the record gets added multiple times.
you should add the id of the child object by an hidden_field tag
<%= f.hidden_field :id %>
and then update with the id :
{project_parcels_attributes"=>[{id: an_id, "parcel_id"=>"680060", "_destroy"=>"1"}, {id: an_id, "parcel_id"=>"680088"}]}

Validate before Posting a text or an image

I am working on a self-learning Rails application (the source code can be found here. I want to validate the presence of the content before posting a text or an image:
.
Those are my models or look below:
class Post < ActiveRecord::Base
belongs_to :user
default_scope { order ("created_at DESC")}
belongs_to :content, polymorphic: true
has_reputation :votes, source: :user, aggregated_by: :sum
end
class PhotoPost < ActiveRecord::Base
has_attached_file :image, styles: {
post: "200x200>"
}
end
class TextPost < ActiveRecord::Base
attr_accessible :body
end
Here are my controllers in case they have a relation with this. Any other files can be found in my Github account. I am sure it will be messy to copy the whole project (that is why I am giving links for the controllers and for my project).
So what I have tried so far. (I tried those on the Posts Model)
=> Using validates_associated
validates_associated :content, :text_post
and getting an error "undefined method `text_post' for #Post:0x517c848>"
=> Used validates
validates :content, :presence => true
and getting no error however a post is created with no text.
validates :body, :presence => true
and getting an error "undefined method `body' for #Post:0x513e4a8>"
If you need any other information please let me know and I will provide it asap.
Thank you.
It would seem you have quite a confusing model setup with some key missing relation rules. E.g. Polymorphic rule which is not being utilised and a has_many relation between User and Post with no sign a of a user_id value in the Post model. Here is how I would set it up:
User.rb
def User << ActiveRecord::Base
has_many :text_posts
has_many :photo_posts
end
TextPost.rb
def TextPost << ActiveRecord::Base
attr_accessible :body, :user_id
belongs_to :user
validates :body, :presence => true
end
PhotoPost.rb
def PhotoPost << ActiveRecord::Base
attr_accessible :image, :user_id
belongs_to :user
validates :file, :presence => true, :format => {
:with => %r{\.(gif|png|jpg)$}i,
:message => "must be a URL for GIF, JPG or PNG image."
}
end
Then in your view you would need to do:
<%= form_for #text_post do |f| %>
# ...
<% end %>
And in your controller you can modify the create method to include the current_user from devise and assign it to the new text post record (user_id attribute):
text_posts_controller.rb
def create
#text_post = current_user.text_posts.new(params[:text_post])
end
This adheres more to the DRY principle which Ruby on Rails excels at - you shouldn't be writing alot of code to just create a new record.
I would advise on reading up on some Ruby on Rails standard and best practises. You shouldn't need to create a method in the Dashboard Model in order to create a new TextPost or PhotoPost record. This is a very confusing way of going about it; instead you should be utilising the power of ActiveRecord relation.
I would advise checking out Railscasts. They have alot of fulfilling content.

How can I validate a parent model, which uses accepts_nested_attributes_for

I have a polymorphic association (contact_details) in my Company model and I want to validate the parent model. Note: I am using accepts_nested_attributes_for in my parent model.
The basic rule:
the company must have at least one phone (phone is the kind of
contact_detail)
The problem:
accepts_nested_attributes_for call destroy for child objects AFTER
validation of the parent object
so the user are able to delete a phone. Of course, later, when the user will try to edit a company without a phone, he/she will get an error (The company must have at least one phone).
Company (Parent) model:
class Company < ActiveRecord::Base
PHONES_NUMBER_MIN = 1
attr_accessible :name, :contact_details_attributes, ...
has_many :contact_details, :as => :contactable, :dependent => :destroy
validate do |company|
check_phones_number
end
accepts_nested_attributes_for :contact_details, :allow_destroy => true, :reject_if => :all_blank
private
def phones_number_valid?
kind = ContactDetail::Kind.phone
phones = contact_details.select { |cd| cd.kind_id == kind.id }
phones.size >= PHONES_NUMBER_MIN
end
def check_phones_number
unless phones_number_valid?
errors.add(:base, :phones_too_short, :count => PHONES_NUMBER_MIN)
end
end
...
end
ContactDetail (Child) model:
class ContactDetail < ActiveRecord::Base
attr_accessible :kind_id, :kind_value_source
belongs_to :contactable, :polymorphic => true
belongs_to :kind
validates :kind_value_source, :presence => true, :length => {:maximum => 255}
...
end
Note: I simplified the original version, so objective was clear to you. Here is the gist with the code.
By using the reject_if option I am able to forbid the deletion of all the phones. It is probably the best option by now. But I want to hear your opinions.
I also found this question and tried to apply the answer, but it didn't helped a lot. The same problem, as I described above. I've drawn a flowchart so you can see the trace, as I see it.
How can I validate the parent model in such a case?
I would be grateful for any help.
From the question you referenced, you can get rid of the reject_if and modify the line in phones_number_valid?:
phones = contact_details.select { |cd| cd.kind_id == kind.id && !cd.marked_for_destruction? }

accepts_nested_attributes_for with polymorphic association

Is there any way to halt saving of child before parent.
I am using accepts_nested_attributes_for with polymorphic association.
I used multiple options validates_presence_of :parent_id , validates_assoicated :parent but none are working.
For example, I do have a class
Class Person
include HasPhoneNumbers
..
end
module HasPhoneNumbers
def self.included(kclass)
kclass.has_many :phone_numbers, :as => :callable, :dependent => kclass == Person ? :destroy : :nullify
end
klass.accepts_nested_attributes_for :phone_numbers, :reject_if => lambda {|pn| pn.keys.any?{|k| k.to_sym != :id && pn[k].blank?} }
end
class PhoneNumber
belongs_to :callable, :polymorphic => true
end
So while saving person due to validation in person object, it was not saving. However, child(phone_number) was saving. So I need to restrict it to not save child(phone_number) before parent(person) saves.
I did try multiple options using validates_presence_of and validates_associated, but none are working for me.
#person = Person.new(params[:person])
ActiveRecord::Base.transaction do
person.save!
end
Wrapping your saves within a transaction should roll back the phone number save if the person fails validation.
Reference: ActiveRecord Transactions

Destroy on blank nested attribute

I would like to destroy a nested model if its attributes are blanked out in the form for the parent model - however, it appears that the ActiveRecord::Callbacks are not called if the model is blank.
class Artist < ActiveRecord::Base
using_access_control
attr_accessible :bio, :name, :tour_dates_attributes
has_many :tour_dates, :dependent => :destroy
accepts_nested_attributes_for :tour_dates, :reject_if => lambda { |a| a[:when].blank? || a[:where].blank? }, :allow_destroy => true
validates :bio, :name :presence => true
def to_param
name
end
end
and
class TourDate < ActiveRecord::Base
validates :address, :when, :where, :artist_id, :presence => true
attr_accessible :address, :artist_id, :when, :where
belongs_to :artist
before_save :destroy_if_blank
private
def destroy_if_blank
logger.info "destroy_if_blank called"
end
end
I have a form for Artist which uses fields_for to show the fields for the artist's associated tour dates, which works for editing and adding new tour dates, but if I merely blank out a tour date (to delete it), destroy_if_blank is never called. Presumably the Artist controller's #artist.update_attributes(params[:artist]) line doesn't consider a blank entity worth updating.
Am I missing something? Is there a way around this?
I would keep the :reject_if block but insert :_destroy => 1 into the attributes hash if your conditions are met. (This is useful in the cases where it's not convenient to add _destroy to the form code.)
You have to do an extra check to see if the record exists in order to return the right value but the following seems to work in all cases for me.
accepts_nested_attributes_for :tour_dates, :reject_if => :reject_tour, :allow_destroy => true
def reject_tour(attributes)
exists = attributes['id'].present?
empty = attributes.slice(:when, :where).values.all?(&:blank?)
attributes.merge!({:_destroy => 1}) if exists and empty # destroy empty tour
return (!exists and empty) # reject empty attributes
end
You could apply when all attributes are blank by just changing the empty calculation to:
empty = attributes.except(:id).values.all?(&:blank?)
I managed to do something like this today. Like #shuriu says, your best option is to remove the reject_if option and handle destruction yourself. mark_for_destruction comes in handy :
class Artist < ActiveRecord::Base
accepts_nested_attributes_for :tour_dates
before_validation :mark_tour_dates_for_destruction
def mark_tour_dates_for_destruction
tour_dates.each do |tour_date|
if tour_date.when.blank? || tour_date.where.blank?
tour_date.mark_for_destruction
end
end
end
end
You have code that says the record should be ignored if the 'where' or the 'when' is blank, on the accepts_nested _attributes line, remove the reject_if and your destroy_if blank will likely be called.
Typically to destroy, you would set a _destroy attribute on the nested record, check out the docs http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Also, just used cocoon for some of this today, and thought it was awesome, https://github.com/nathanvda/cocoon
Similar to Steve Kenworthy's answer, no local variables.
accepts_nested_attributes_for :tour_dates, :reject_if => :reject_tour, :allow_destroy => true
def reject_tour(attributes)
if attributes[:when].blank? || attributes[:where].blank?
if attributes[:id].present?
attributes.merge!({:_destroy => 1}) && false
else
true
end
end
end
With your current code it's not possible, because of the reject_if option passed to accepts_nested_attributes_for.
As Christ Mohr said, the easiest way is to set the _destroy attribute for the nested model when updating the parent, and the nested model will be destroyed. Refer to the docs for more info on this, or this railscast.
Or you can use a gem like cocoon, or awesome_nested_fields.
To do specifically what you want, you should remove the reject_if option, and handle the logic in a callback inside the parent object. It should check for blank values in the tour_dates_attributes and destroy the nested model. But tread carefully...

Resources