Skip saving associated object if invalid on update - ruby-on-rails

I have a person table and address table like this:
class Person < ApplicationRecord
has_many :addresses, dependent: :destroy
accepts_nested_attributes_for :addresses
end
class Address < ApplicationRecord
belongs_to :person
validates :address_line_1, presence: true
end
In my controller, I want to update the person and the associated addresses, but if the address is invalid and the person is valid, I'd still want to update the Person object and keep the invalid address the same as before without running into a ROLLBACK.
What's the best way to handle this? I realize I can do some logic to check if the address is invalid and remove the addresses_attributes from the parameters, then assign the parameters again and save it, but is there any better way?

has_many association has a validate option you can set to false and handle validations however you want https://guides.rubyonrails.org/association_basics.html#options-for-has-many-validate
I think you are using accepts_nested_attributes_for since you named the addresses_attributes param, personally I wouldn't combine no validation with that, you may end up with invalid addresses.
Personally, I would do two step (with the default validate: true config):
first update only the user's attributes
call save on the user (so addresses doesn't mess the update up)
set the addresses attributes
call save on the user (so everything gets validated again)
EDIT: if you want to use the validate: false option you may want to set autosave: false too so you don't save invalid addresses https://guides.rubyonrails.org/association_basics.html#options-for-has-many-autosave

Ultimately you'll either want to check that the address attributes are valid and remove them if not OR saved the records separately.
A common pattern is to opt for second option using a form object. This helps keep the logic out of the controller and makes it easier to expand on updating a Person in the future.

Related

rails : validation vs reject_if in nested forms

I have a model A which accepts nested attributes for model B. The number of model B entries and whether an entry is required or not is decided based on a 3rd table in db. I use form_for to generate a form with fields_for for model B entries. This is how the validation needs to work:
An entry for model B is allowed to be blank if its corresponding :required field is false, but it should not be saved, i.e, this entry should not raise validation error but should be rejected by :reject_if.
An entry whose :required field is true is not allowed to be blank. These entries should not be rejected by the :reject_if but raise a validation error when it is being saved to db.
class modelA < ApplicationRecord
has_many :modelBs, dependent: :destroy
accepts_nested_attributes_for :modelBs, reject_if: :not_required_and_blank?
def not_required_and_blank?
# return true if modelB.entry is blank && modelB.required_entry? is false
end
end
class modelB < ApplicationRecord
belongs_to :modelA
validates :modelB_entry, presence: true, if: :required_entry?
def required_entry?
ModelC.find(:id).required
end
end
Should I do this
in the :reject_if option of accepts_nested_attributes_for (in modelA class) and in validates_presence_of modelB_entry method (in modelB class) OR
everything in :reject_if OR
everything in :validates_presence_of ?
Which gets executed first :reject_if or vaildation?
reject_if and validations have different purpose.
suppose you have a nested model modelB with two fields name and title, with name as mandatory field.
Use of reject_if
In the controller #new action, you decide to build the first nested object yourself to give user a better UI experience so that he can easily see what else he can add to the form. or the user himself clicks on the "+ Add more" button. But then decides to leave modelB blank and not entering any info in it. Now, if you don't use reject_if but you have validations the user will get the error message saying field is blank, and he won't be able to submit the form, unless he clicks on remove button.
So, in this case you will check if all fields are blank in the reject_if, if that is the case we can ignore the record, else let the model do it's work.
Also, keep in mind that reject_if does not guarantees data integrity. As any typical RoR app has multiple entry points.
In my opinion, you should go with the first option. Use both reject_if and validations

Rails: handling object creation via candidate key

I have a table foo which belongs_to :bar (bar has_many :foos).
There are too many foos for it to be sensible to offer the user a list when they're creating a bar, so I'm going to get the user to enter the name of the bar that the foo belongs to. The name is a candidate key -- guaranteed to be present and unique.
I think I can see the steps I need to take: make foo validate that bar_id is present, and somewhere put a find_where to look up the bar that has the given name (if it's not present I'll get a nil, so the validation should fail -- good).
What I want to know is where's the best place to put that find_where in order to check all creations and edits of foo whilst maintaining DRY principles? How to I join it up to the new and edit forms? (Or is there a more Rails way?)
Rails 4, by the way.
EDIT:
To make it more concrete, suppose I have a list of every airport in the world, in Airport, which contains the airport name (name), the 3-character identifier that gets put on your luggage tag (code), and the Rails-generated airport_id. And suppose I have Departure which belongs_to :airport. When I create/edit a departure, the user enters the airport code, which I have to map to an actual entry in Airport to create/edit the entry. What's the best place/way to do that?
You don't actually need to do all of that, Rails has a way! When you specify an association between two models in your database, it suffices to simply add the dependent model name to a validation:
class Foo < ActiveRecord::Base
belongs_to :bar, validate: true
end
This will cause ActiveRecord to validate the presence of the bar object in question. And, because this is Rails, there are other ways of writing this type of validation:
With validates_associated
class Foo < ActiveRecord::Base
belongs_to :bar
validates_associated :bar
end
or with validates presence
class Foo < ActiveRecord::Base
belongs_to :bar, inverse_of :foo
validates :bar, presence: true
end
class Bar < ActiveRecord::Base
has_many :foo, inverse_of :bar
end
Beware a couple gotchas:
In the validates_associated construction, you must make sure to only specify the validation on one of your two related models. If you specify the validates_associated attribute on both, your validations will call each other infinitely.
In the validates presence version, you must specify the inverse_of relationship in order to validate the presence of the associated AR model.
EDIT
In response to your edited question, I've updated my answer:
You'll want to query the Airport by its code attribute in your DeparturesController, under the #create and #update actions:
class DeparturesController < ApplicationController
def create
airport = Airport.find_by(code: params[:departure][:code])
if airport
departure = airport.departures.build(params[:departure])
if departure.save
...
else
...
end
else
...
end
end
end
The upshot of using the find_by method in ActiveRecord is that it returns nil if the search fails, making it easy for you to write error-handling logic in the event that your user enters an invalid code.

Factory Girl with polymorphic association for has_many and has_one

I am currently working on a project and I wanted to create tests using factory girl but I'm unable to make it work with polymorphic has_many association. I've tried many different possibilities mentioned in other articles but it still doesn't work. My model looks like this:
class Restaurant < ActiveRecord::Base
has_one :address, as: :addressable, dependent: :destroy
has_many :contacts, as: :contactable, dependent: :destroy
accepts_nested_attributes_for :contacts, allow_destroy: true
accepts_nested_attributes_for :address, allow_destroy: true
validates :name, presence: true
#validates :address, presence: true
#validates :contacts, presence: true
end
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
# other unimportant validations, address is created valid, the problem is not here
end
class Contact < ActiveRecord::Base
belongs_to :contactable, polymorphic: true
# validations ommitted, contacts are created valid
end
So bassically I want to create factory for Restaurant with address and contacts (with validations on Restaurant for presence, but if it's not possible, even without them) but I'm unable to do so. Final syntax should be like:
let(:restaurant) { FactoryGirl.create(:restaurant) }
Which should also create associated address and contacts. I have read many articles but I always get some sort of error. Currently my factories (sequences are defined correctly) are like this:
factory :restaurant do
name
# address {FactoryGirl.create(:address, addressable: aaa)}
# contacts {FactoryGirl.create_list(:contact,4, contactable: aaa)}
# Validations are off, so this callback is possible
after(:create) do |rest|
# Rest has ID here
rest.address = create(:restaurant_address, addressable: rest)
rest.contacts = create_list(:contact,4, contactable: rest)
end
end
factory :restaurant_address, class: Address do
# other attributes filled from sequences...
association :addressable, factory: :restaurant
# addressable factory: restaurant
# association(:restaurant)
end
factory :contact do
contact_type
value
association :contactable, :factory => :restaurant
end
Is there a way to create restaurant with one command in test with addresses and contacts set? Do I really need to get rid off my validations because of after(:create) callback?
Current state is as fllowing:
Restaurant is created with name and id.
Than the address is being created - all is correcct, it has all the values including addressable_id and addressable_type
After that all contacts are being creaed, again everything is fine, cntacts has the right values.
After that, restaurant doesn't have any ids from associated objects, no association to adddress or contacts
After than, for some reason restaurant is build again (maybe to add those associations?) and it fails: I get ActiveRecord:RecordInvalid.
I'm using factory_girl_rails 4.3.0, ruby 1.9.3 and rails 4.0.1. I will be glad for any help.
UPDATE 1:
Just for clarification of my goal, I want to be able to create restaurant in my spec using one command and be able to access associated address and contacts, which should be created upon creation of restaurant. Let's ignore all validations (I had them commented out in my example from the beginning). When I use after(:build), first restaurant is created, than address is created with restaurant's ID as addressable_id and class name as addressable_type. Same goes to contacts, all are correct. The problem is, that restaurant doesn't know about them (it has no IDs of address or contacts), I can't access them from restaurant which I want to.
After really thorough search I have found an answer here - stackoverflow question.This answer also point to this gist. The main thing is to build associations in after(:build) callback and then save them in after(:create) callback. So it looks like this:
factory :restaurant do
name
trait :confirmed do
state 1
end
after(:build) do |restaurant|
restaurant.address = build(:restaurant_address, addressable: restaurant)
restaurant.contacts = build_list(:contact,4, contactable: restaurant)
end
after(:create) do |restaurant|
restaurant.contacts.each { |contact| contact.save! }
restaurant.address.save!
end
end
I had also a bug in my rspec, because I was using before(:each) callback instead of before(:all). I hope that this solution helps someone.
The Problem
Validating the length of a related list of rows is a difficult problem to frame in SQL, so it's a difficult problem to frame in ActiveRecord as well.
If you're storing a restaurant foreign key on the addresses table, you can't ever actually create a restaurant that has addresses by the time it's saved, because you need to save the restaurant to get its primary key. You can get around this problem in ActiveRecord by building up the associated objects in memory, validating against those, and then committing the entire object graph in one SQL transaction.
How to do what you're asking
You can generally get around this by moving things into an after(:build) hook instead of after(:create). ActiveRecord will save its dependent has_one and has_many associations once it saves itself.
You're getting errors now because you can't modify an object to satisfy validations in an after(:create) block, because validations have already run by the time the callback runs.
You can change your restaurant factory to look something like this:
factory :restaurant do
name
after(:build) do |restaurant|
restaurant.address = build(:restaurant_address, addressable: nil)
restaurant.contacts = build(:contact, 4, contactable: nil)
end
end
The nils there are to break the cyclic relationship between the factories. If you do it this way, you can't have a validation on the addressable_id or contactable_id keys, because they won't be available until the restaurant is saved.
Alternatives
Although you can get both ActiveRecord and FactoryGirl to do what you're asking, it sets up a precarious list of dependencies which are difficult to understand and are likely to result in leaky validations or unexpected errors like the ones you're seeing now.
If you're validating contacts this way from the restaurant model because of a form in which you create both a restaurant and its corresponding contacts, you can save yourself a lot of pain by creating a new ActiveModel object to represent that form. You can collect the attributes you need for each object there, move some of the validations (especially the ones which validate the length of the contacts list), and then create the object graph on that form in a way that's much clearer and less likely to break.
This has the added benefit of making it easy to create lightweight restaurant objects in other tests which don't need to worry about contacts or addresses. If you force your factories to create these dependent objects every time, you'll quickly run into two problems:
Your tests will be painfully slow. Creating five dependent records every time you want to work with a restaurant won't scale very far.
If you ever want to specify different contacts or addresses in your tests, you'll constantly be fighting with your factories.

Unique children in has_many validation in rails 4

I'm using the Google places API to collect any number of locations on a profile. Each location has the profile_id, 4 address fields and a lat and long. I want to make sure that all locations for each profile are unique.
At the moment I'm using the code below to validate the uniqueness of the 4 address fields which is working perfectly however this results in a validation error being returned to the view. I would rather save the profile and locations (with duplicates removed) without returning an error to the user.
What is the best way of going about this? Is there a rails approach or should I create my own before-validation function?
class Profile < ActiveRecord::Base
has_many :locations
accepts_nested_attributes_for :locations, allow_destroy: true
etc etc
end
class Location < ActiveRecord::Base
belongs_to :profile
# Only allow unique locations for each profile
validates_uniqueness_of :profile_id, scope: [:sublocality, :locality, :administrative_area_level_1, :country]
etc etc
end
You can check for error[:profile_id].present? in after_validation callback.
If error related to profile_id is present - remove duplicates and save again
You should be careful if you have other validations related to profile_id and check for presence of specific error message in that case.

How to specify fields to set for accepts_nested_attributes_for in ActiveRecord

I have something like:
class Profile < ActiveRecord::Base
belongs_to :user
delegate :full_name, :to => :user
accepts_nested_attributes_for :user
.......
This work fine as I want profile to be able to set first_name and last_name in user. But this poses security threat if user injects other parameters in the form.
How to make accepts_nested_attributes_for only takes first_name and last_name and drop other paramters?
There are two options that I can think of and they are actually the same way you would handle the mass assignment without going through anaf.
accepts_nested_attributes_for will respect attrs_accessible on the user model. If you specify that an attribute is not exposed to mass assignment on the user model, it will not be able to be assigned through accepts_nested_attributes_for either.
You can handle the sanitation in your profiles controller. Attributes that will be passed to the user model will come in under params[:profile][:user_attributes]. You can then either use slice to only get the attributes you want to allow, or use except to delete the attributes you don't want to allow. Though I prefer to whitelist allowed attributes vs blacklisting.
Option 1 would affect anywhere you are using mass assignment for the user model, while option 2 would only affect parameters coming in through the profiles controller

Resources