validates_presence_of and unsaved associations - ruby-on-rails

Take the following code (Rails 3.0.10):
User < AR
has_many :providers
Provider < AR
belongs_to :user
validates_presence_of :user
user = User.new
user.providers.build
# so both models not yet saved but associated with each other
user.valid?
=> false
user.errors
=> {:providers=>["is invalid"]}
user.providers.first.errors
=> {:user_id=>["can't be blank"]}
Why can't Provider see that it has a not yet saved associated user model available? Or in other words - how can I deal with that so that the validation is still present? Or maybe I'm doing something wrong?
Note, that I'm looking for a clean solution, so suggesting a before validation callback in Provider model saving the User model to the database is a no-go.

Use :inverse_of
class User < ActiveRecord::Base
has_many :providers, :inverse_of => :user
end
class Provider < ActiveRecord::Base
belongs_to :user, :inverse_of => :providers
validates :user, :presence => true
end

Related

Validate associated object (lazy validation)

I'm trying to solve validation of associated object with condition.
User doesn't need to have filled author_bio until he is author. So app needs to ensure, that author can't create post without author_bio and author_bio can't be deleted if user already created any post.
class User < ApplicationRecord
has_many :posts, foreign_key: 'author_id', inverse_of: :author
validates :author_bio, presence: { if: :author? }
def author?
posts.any?
end
end
class Post < ApplicationRecord
belongs_to :author, class_name: 'User', inverse_of: :posts, required: true
end
Unfortunately this doesn't validate author on creation of new post:
user = User.first
user.author_bio
=> nil
post = Post.new(author: user)
post.valid?
=> true
post.save
=> true
post.save
=> false
post.valid?
=> false
So how can I prevent creating of new post by user without author_bio? I can add second validation to Post model, but this is not DRY. Is there any better solution?
The answer here seems to be use of validates_associated once you have your associations correctly set up (including inverse_of which you have, but stating for others, rails in many cases misses them or creates them incorrectly)
so to adjust the classes here:
class User < ApplicationRecord
has_many :posts, foreign_key: 'author_id', inverse_of: :author
validates :author_bio, presence: { if: :author? }
def author?
posts.any?
end
end
class Post < ApplicationRecord
belongs_to :author, class_name: 'User', inverse_of: :posts
validates :author, presence: true
validates_associated :author
end
Now when you try running what you did before:
user = User.first
user.author_bio
=> nil
post = Post.new(author: user)
post.valid?
=> false
post.save
=> false
Does not allow you to save since author_bio is empty
Only thing to watch out there was to set up correct associations, otherwise rails was confused and skipping validation on User class since it thought the relationship is not yet in existence.
NOTE: I removed required: true from belongs_to since in rails 5 is default, therefore you won't need validates :author, presence: true either only in rails 5.

Validation on associtions in rails with roles

So I have two ActiveRecord classes
class User < ActiveRecord::Base
has_many :buyer_deals, :class_name => "Deal", :foreign_key => :buyer_id
has_many :seller_deals, :class_name => "Deal", :foreign_key => :seller_id
validates_presence_of :name # THIS SHOULD ONLY BE RUN IF USER IS A SELLER
# IN THE DEAL
validates_presence_of :phone # THIS SHOULD ONLY BE RUN IF USER IS A BUYER
# IN THE DEAL
end
class Deal < ActiveRecord::Base
belongs_to :seller, :class_name => 'User'
belongs_to :buyer, :class_name => 'User'
validates_associated :seller
validates_associated :buyer
end
What I want to do is create a new deal with.
Deal.create(A NICE STRUCT WITH SELLER AND BUYER)
However I only want to run the name validation if the relation from the deal is a seller and the phone if the the relation from the deal is a seller, is this possible in rails, does not seem to find anything in the documentation.
You should be able to do this by adding a condition to you validation.
So, your User class would wind up looking something like...
class User < ActiveRecord::Base
has_many :buyer_deals, :class_name => "Deal", :foreign_key => :buyer_id
has_many :seller_deals, :class_name => "Deal", :foreign_key => :seller_id
validates_presence_of :name, :if => :has_an_active_seller_deal?
validates_presence_of :phone, :if => :has_an_active_buyer_deal?
def has_active_seller_deals?
seller_deals.count > 0
end
def has_active_buyer_deals?
buyer_deals.count > 0
end
end
An alternative to this would be to simply require all users to have a name and phone number on file at all times (no conditional validation), and only reveal it to other users with which they had active deals, and not as part of a user's public profile, thereby protecting the user's privacy when possible. This would probably be simpler.
You could put the validations in a callback:
before_save :check_user_type
private
def check_user_type
user_type = self.responds_to?(seller_id) ? :seller : :buyer
if user_type == :seller
validates_presence_of :name
else
validates_presence_of :phone
end

Validate presence of associated model

I don't get it I have the following models:
class Seller < ActiveRecord::Base
has_many :cars, :dependent => :destroy
end
class Car < ActiveRecord::Base
belongs_to :seller
# I have tried both with the validates existence gem:
validates :existence => {:allow_nil => false}
# And normally...
validates_presence_of :seller
end
But nothing works if I do the following:
seller = Seller.new()
seller.cars.build()
seller.save # I get => false #messages={:seller=>["does not exist"], :seller_id=>["does not exist"]}
I should be able to do this right?
It's like - it's validating the associated model before the mother-object has been saved - and i have NOT defined a validates_associated or something like that.
Any clue? Or am I getting the order of saving and validating all wrong?
I have run into this in the past, and have used "inverse_of" to solve it. You also need "accepts_nested_attributes_for". So in the seller, you would want to change your code to the following:
class Seller < ActiveRecord::Base
has_many :cars, :dependent => :destroy, :inverse_of => :seller
accepts_nested_attributes_for :cars
end
Seller does not exist because it has not been saved in the database, it's just in the memory, and so Car does not know Seller's id which it needs to know - it has to add it to the seller_id column. So you first have to save Seller and you don't need the validates_presence_of :seller call in Car.

Rails Associations Not working and not recognizing classes

I have a weird bug that just popped up with my rails app that I cannot figure out. I recently added a new association to an existing Model and now my previous associations do not want to work properly.
#=> self.user
#=> <# user.id => "1" ...
#=> self.transactions
#=> [<# transaction_id => "1"...
#=> self.credit_plan
#=> nil
So the first two associations work fine via, but for some reason credit_plan returns nil and is crashing all my existing working code. Here is the record associations I have.
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :credit_plan
has_many :transactions, :class_name => "OrderTransaction"
.
class CreditPlan < ActiveRecord::Base
scope :active, where({:is_active => true})
scope :inactive, where({:is_active => false})
has_many :orders, :class_name => "Order"
.
class OrderTransaction < ActiveRecord::Base
belongs_to :order
serialize :params
Alright Guys, I figured it out. If I had posted more context of my files, I'm sure someone would have figured it out and helped me sooner.
So basically, when I was setting up my virtual attributes for the credit card form, I accidentally stomped on my own name space by adding :credit_plan as an attribute, which overrides the association.
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :credit_plan
has_many :transactions, :class_name => "OrderTransaction"
validates_presence_of :credit_plan_id, :user
attr_accessor :first_name, :last_name, :card_type, :credit_card,
:number, :verification_value, :promotional_code, :expires_on,
:credit_plan # << This will override associations, delete to fix.
validate :validate_card, :on => :create

How do I use an in-memory object when working with :has_many and :belongs_to

I am using Active Record with Rails 3. I have a class User that has_many Categories. I would like to instantiate categories from user that shares the same in-memory user instance.
The default Active record behavior creates a new instance of user for each category. There is any way to change this behavior?
Here are some snippets of my code. You can notice that I tried to use :inverse_of, without success...
class Category < ActiveRecord::Base
attr_accessor :name
attr_accessible :name, :user
belongs_to :user, :inverse_of => :categories
end
class User < ActiveRecord::Base
attr_accessor :key
attr_accessible :categories, :key
has_many :categories, :inverse_of => :user
end
I have the following spec to test the desired behavior:
user = User.first
user.key = #key # key is an :attr_accessor, not mapped to db
category = user.categories.first
category.user.key.should == user.key # saddly, it fails.. :(
Any suggestions?
Thank you for reading my question!

Resources